1use crate::{Feature, Value};
10
11pub fn to_pretty_json(value: &Value) -> String {
13 let mut out = String::new();
14 write_pretty(value, 0, &mut out);
15 out.push('\n');
16 out
17}
18
19fn write_pretty(value: &Value, indent: usize, out: &mut String) {
20 match value {
21 Value::Object(m) if m.is_empty() => out.push_str("{}"),
22 Value::Array(a) if a.is_empty() => out.push_str("[]"),
23 Value::Object(m) => {
24 out.push_str("{\n");
25 for (i, (k, v)) in m.iter().enumerate() {
26 indent_by(indent + 1, out);
27 out.push_str(&Value::Str(k.clone()).to_json());
28 out.push_str(": ");
29 write_pretty(v, indent + 1, out);
30 if i + 1 < m.len() {
31 out.push(',');
32 }
33 out.push('\n');
34 }
35 indent_by(indent, out);
36 out.push('}');
37 }
38 Value::Array(a) => {
39 out.push_str("[\n");
40 for (i, v) in a.iter().enumerate() {
41 indent_by(indent + 1, out);
42 write_pretty(v, indent + 1, out);
43 if i + 1 < a.len() {
44 out.push(',');
45 }
46 out.push('\n');
47 }
48 indent_by(indent, out);
49 out.push(']');
50 }
51 scalar => out.push_str(&scalar.to_json()),
52 }
53}
54
55fn indent_by(level: usize, out: &mut String) {
56 for _ in 0..level {
57 out.push_str(" ");
58 }
59}
60
61pub fn flatten(value: &Value) -> Vec<(String, String)> {
64 let mut out = Vec::new();
65 walk("", value, &mut out);
66 out
67}
68
69fn walk(prefix: &str, value: &Value, out: &mut Vec<(String, String)>) {
70 match value {
71 Value::Object(m) => {
72 for (k, v) in m {
73 walk(&join_key(prefix, k), v, out);
74 }
75 }
76 Value::Array(a) => {
77 for (i, v) in a.iter().enumerate() {
78 walk(&join_key(prefix, &i.to_string()), v, out);
79 }
80 }
81 scalar => out.push((prefix.to_string(), scalar_string(scalar))),
82 }
83}
84
85fn join_key(prefix: &str, key: &str) -> String {
86 if prefix.is_empty() {
87 key.to_string()
88 } else {
89 format!("{prefix}.{key}")
90 }
91}
92
93pub fn scalar_string(value: &Value) -> String {
95 value.to_raw_string()
96}
97
98pub fn has_nesting(value: &Value) -> bool {
100 match value {
101 Value::Object(m) => m.iter().any(|(_, v)| is_container(v)),
102 Value::Array(a) => a.iter().any(is_container),
103 _ => false,
104 }
105}
106
107pub fn has_array(value: &Value) -> bool {
109 match value {
110 Value::Array(_) => true,
111 Value::Object(m) => m.iter().any(|(_, v)| has_array(v)),
112 _ => false,
113 }
114}
115
116fn is_container(value: &Value) -> bool {
117 matches!(value, Value::Object(_) | Value::Array(_))
118}
119
120pub fn features_used(value: &Value) -> Vec<Feature> {
125 let mut used = Vec::new();
126 if has_array(value) {
127 used.push(Feature::Arrays);
128 }
129 if has_nesting(value) {
130 used.push(Feature::Nesting);
131 }
132 if has_typed_scalar(value) {
133 used.push(Feature::TypedScalars);
134 }
135 used
136}
137
138fn has_typed_scalar(value: &Value) -> bool {
140 match value {
141 Value::Int(_) | Value::Float(_) | Value::Bool(_) | Value::Null => true,
142 Value::Str(_) => false,
143 Value::Array(a) => a.iter().any(has_typed_scalar),
144 Value::Object(m) => m.iter().any(|(_, v)| has_typed_scalar(v)),
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn flatten_to_dotted_keys() {
154 let v = Value::Object(vec![
155 ("a".into(), Value::Object(vec![("b".into(), Value::Int(1))])),
156 (
157 "c".into(),
158 Value::Array(vec![Value::Str("x".into()), Value::Str("y".into())]),
159 ),
160 ]);
161 assert_eq!(
162 flatten(&v),
163 vec![
164 ("a.b".to_string(), "1".to_string()),
165 ("c.0".to_string(), "x".to_string()),
166 ("c.1".to_string(), "y".to_string()),
167 ]
168 );
169 }
170
171 #[test]
172 fn pretty_json_indents() {
173 let v = Value::Object(vec![(
174 "a".into(),
175 Value::Array(vec![Value::Int(1), Value::Int(2)]),
176 )]);
177 assert_eq!(to_pretty_json(&v), "{\n \"a\": [\n 1,\n 2\n ]\n}\n");
178 assert_eq!(to_pretty_json(&Value::Object(vec![])), "{}\n");
179 }
180
181 #[test]
182 fn feature_detection() {
183 let nested = Value::Object(vec![("a".into(), Value::Object(vec![]))]);
184 assert!(has_nesting(&nested));
185 assert!(!has_nesting(&Value::Object(vec![(
186 "a".into(),
187 Value::Int(1)
188 )])));
189 assert!(has_array(&Value::Object(vec![(
190 "a".into(),
191 Value::Array(vec![])
192 )])));
193 }
194}