toon/cli/
json_stringify.rs1use std::fmt::Write;
2
3use crate::JsonValue;
4
5#[must_use]
8pub fn json_stringify_lines(value: &JsonValue, indent: usize) -> Vec<String> {
9 let estimated_size = estimate_json_size(value, indent);
11 let mut buf = String::with_capacity(estimated_size);
12 stringify_value_to_buf(value, 0, indent, &mut buf);
13 vec![buf]
14}
15
16fn estimate_json_size(value: &JsonValue, indent: usize) -> usize {
18 match value {
19 JsonValue::Primitive(p) => match p {
20 crate::StringOrNumberOrBoolOrNull::Null => 4,
21 crate::StringOrNumberOrBoolOrNull::Bool(_) => 5,
22 crate::StringOrNumberOrBoolOrNull::Number(_) => 20,
23 crate::StringOrNumberOrBoolOrNull::String(s) => s.len() + 10,
24 },
25 JsonValue::Array(items) => {
26 let base = items
27 .iter()
28 .map(|v| estimate_json_size(v, indent))
29 .sum::<usize>();
30 base + items.len() * (2 + indent) + 4
31 }
32 JsonValue::Object(entries) => {
33 let base: usize = entries
34 .iter()
35 .map(|(k, v)| k.len() + 4 + estimate_json_size(v, indent))
36 .sum();
37 base + entries.len() * (2 + indent) + 4
38 }
39 }
40}
41
42fn stringify_value_to_buf(value: &JsonValue, depth: usize, indent: usize, buf: &mut String) {
43 match value {
44 JsonValue::Primitive(primitive) => {
45 stringify_primitive_to_buf(primitive, buf);
46 }
47 JsonValue::Array(values) => stringify_array_to_buf(values, depth, indent, buf),
48 JsonValue::Object(entries) => stringify_object_to_buf(entries, depth, indent, buf),
49 }
50}
51
52fn stringify_array_to_buf(values: &[JsonValue], depth: usize, indent: usize, buf: &mut String) {
53 if values.is_empty() {
54 buf.push_str("[]");
55 return;
56 }
57
58 buf.push('[');
59
60 if indent > 0 {
61 for (idx, value) in values.iter().enumerate() {
62 buf.push('\n');
63 push_indent(buf, (depth + 1) * indent);
64 stringify_value_to_buf(value, depth + 1, indent, buf);
65 if idx + 1 < values.len() {
66 buf.push(',');
67 }
68 }
69 buf.push('\n');
70 push_indent(buf, depth * indent);
71 } else {
72 for (idx, value) in values.iter().enumerate() {
73 stringify_value_to_buf(value, depth + 1, indent, buf);
74 if idx + 1 < values.len() {
75 buf.push(',');
76 }
77 }
78 }
79 buf.push(']');
80}
81
82fn stringify_object_to_buf(
83 entries: &[(String, JsonValue)],
84 depth: usize,
85 indent: usize,
86 buf: &mut String,
87) {
88 if entries.is_empty() {
89 buf.push_str("{}");
90 return;
91 }
92
93 buf.push('{');
94
95 if indent > 0 {
96 for (idx, (key, value)) in entries.iter().enumerate() {
97 buf.push('\n');
98 push_indent(buf, (depth + 1) * indent);
99 push_json_string(buf, key);
101 buf.push_str(": ");
102 stringify_value_to_buf(value, depth + 1, indent, buf);
103 if idx + 1 < entries.len() {
104 buf.push(',');
105 }
106 }
107 buf.push('\n');
108 push_indent(buf, depth * indent);
109 } else {
110 for (idx, (key, value)) in entries.iter().enumerate() {
111 push_json_string(buf, key);
112 buf.push(':');
113 stringify_value_to_buf(value, depth + 1, indent, buf);
114 if idx + 1 < entries.len() {
115 buf.push(',');
116 }
117 }
118 }
119 buf.push('}');
120}
121
122fn stringify_primitive_to_buf(value: &crate::JsonPrimitive, buf: &mut String) {
123 match value {
124 crate::StringOrNumberOrBoolOrNull::Null => buf.push_str("null"),
125 crate::StringOrNumberOrBoolOrNull::Bool(true) => buf.push_str("true"),
126 crate::StringOrNumberOrBoolOrNull::Bool(false) => buf.push_str("false"),
127 crate::StringOrNumberOrBoolOrNull::Number(n) => {
128 if let Some(num) = serde_json::Number::from_f64(*n) {
129 buf.push_str(&num.to_string());
130 } else {
131 buf.push_str("null");
132 }
133 }
134 crate::StringOrNumberOrBoolOrNull::String(s) => {
135 push_json_string(buf, s);
136 }
137 }
138}
139
140#[inline]
142fn push_indent(buf: &mut String, count: usize) {
143 for _ in 0..count {
144 buf.push(' ');
145 }
146}
147
148fn push_json_string(buf: &mut String, s: &str) {
150 buf.push('"');
151 for c in s.chars() {
152 match c {
153 '"' => buf.push_str("\\\""),
154 '\\' => buf.push_str("\\\\"),
155 '\n' => buf.push_str("\\n"),
156 '\r' => buf.push_str("\\r"),
157 '\t' => buf.push_str("\\t"),
158 c if c.is_control() => {
159 let _ = write!(buf, "\\u{:04x}", c as u32);
161 }
162 c => buf.push(c),
163 }
164 }
165 buf.push('"');
166}