1use crate::pycompat::py_float_repr;
23use serde_json::Value;
24use std::fmt::Write;
25
26pub fn py_float(x: f64) -> Value {
30 Value::Number(serde_json::Number::from_f64(x).expect("finite float"))
31}
32
33pub fn dumps_indent2(value: &Value) -> String {
35 let mut out = String::new();
36 write_value(&mut out, value, true, Some(0));
37 out
38}
39
40pub fn dumps_indent2_no_ascii(value: &Value) -> String {
43 let mut out = String::new();
44 write_value(&mut out, value, false, Some(0));
45 out
46}
47
48pub fn dumps_compact(value: &Value) -> String {
51 let mut out = String::new();
52 write_value(&mut out, value, false, None);
53 out
54}
55
56pub fn dumps_canonical_sorted(value: &Value) -> String {
61 let mut out = String::new();
62 write_canonical(&mut out, value);
63 out
64}
65
66fn write_canonical(out: &mut String, value: &Value) {
67 match value {
68 Value::Array(items) => {
69 out.push('[');
70 for (i, item) in items.iter().enumerate() {
71 if i > 0 {
72 out.push(',');
73 }
74 write_canonical(out, item);
75 }
76 out.push(']');
77 }
78 Value::Object(map) => {
79 let mut keys: Vec<&String> = map.keys().collect();
80 keys.sort();
81 out.push('{');
82 for (i, key) in keys.iter().enumerate() {
83 if i > 0 {
84 out.push(',');
85 }
86 write_string(out, key, false);
87 out.push(':');
88 write_canonical(out, &map[key.as_str()]);
89 }
90 out.push('}');
91 }
92 other => write_value(out, other, false, None),
93 }
94}
95
96fn write_value(out: &mut String, value: &Value, ensure_ascii: bool, indent: Option<usize>) {
97 match value {
98 Value::Null => out.push_str("null"),
99 Value::Bool(true) => out.push_str("true"),
100 Value::Bool(false) => out.push_str("false"),
101 Value::Number(n) => write_number(out, n),
102 Value::String(s) => write_string(out, s, ensure_ascii),
103 Value::Array(items) => {
104 if items.is_empty() {
105 out.push_str("[]");
106 return;
107 }
108 out.push('[');
109 for (i, item) in items.iter().enumerate() {
110 if i > 0 {
111 out.push_str(item_sep(indent));
112 }
113 open_line(out, indent, 1);
114 write_value(out, item, ensure_ascii, indent.map(|d| d + 1));
115 }
116 open_line(out, indent, 0);
117 out.push(']');
118 }
119 Value::Object(map) => {
120 if map.is_empty() {
121 out.push_str("{}");
122 return;
123 }
124 out.push('{');
125 for (i, (key, item)) in map.iter().enumerate() {
126 if i > 0 {
127 out.push_str(item_sep(indent));
128 }
129 open_line(out, indent, 1);
130 write_string(out, key, ensure_ascii);
131 out.push_str(": ");
132 write_value(out, item, ensure_ascii, indent.map(|d| d + 1));
133 }
134 open_line(out, indent, 0);
135 out.push('}');
136 }
137 }
138}
139
140fn item_sep(indent: Option<usize>) -> &'static str {
142 match indent {
143 Some(_) => ",",
144 None => ", ",
145 }
146}
147
148fn open_line(out: &mut String, indent: Option<usize>, extra: usize) {
150 if let Some(depth) = indent {
151 out.push('\n');
152 for _ in 0..(depth + extra) * 2 {
153 out.push(' ');
154 }
155 }
156}
157
158fn write_number(out: &mut String, n: &serde_json::Number) {
159 if let Some(i) = n.as_i64() {
160 out.push_str(&i.to_string());
161 } else if let Some(u) = n.as_u64() {
162 out.push_str(&u.to_string());
163 } else {
164 out.push_str(&py_float_repr(n.as_f64().expect("number is f64")));
165 }
166}
167
168fn write_string(out: &mut String, s: &str, ensure_ascii: bool) {
169 out.push('"');
170 for c in s.chars() {
171 match c {
172 '"' => out.push_str("\\\""),
173 '\\' => out.push_str("\\\\"),
174 '\u{8}' => out.push_str("\\b"),
175 '\t' => out.push_str("\\t"),
176 '\n' => out.push_str("\\n"),
177 '\u{c}' => out.push_str("\\f"),
178 '\r' => out.push_str("\\r"),
179 c if (c as u32) < 0x20 => write!(out, "\\u{:04x}", c as u32).unwrap(),
180 c if ensure_ascii && (c as u32) > 0x7e => {
181 let cp = c as u32;
186 if let Some(sur) = crate::pycompat::sentinel_surrogate(c) {
187 write!(out, "\\u{sur:04x}").unwrap();
188 } else if cp <= 0xffff {
189 write!(out, "\\u{cp:04x}").unwrap();
190 } else {
191 let v = cp - 0x10000;
192 let hi = 0xd800 + (v >> 10);
193 let lo = 0xdc00 + (v & 0x3ff);
194 write!(out, "\\u{hi:04x}\\u{lo:04x}").unwrap();
195 }
196 }
197 c => out.push(c),
198 }
199 }
200 out.push('"');
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use serde_json::json;
207
208 #[test]
209 fn indent2_layout() {
210 let v = json!({"a": [], "b": {}, "c": [1], "d": {"x": 1}});
211 assert_eq!(
212 dumps_indent2(&v),
213 "{\n \"a\": [],\n \"b\": {},\n \"c\": [\n 1\n ],\n \"d\": {\n \"x\": 1\n }\n}"
214 );
215 }
216
217 #[test]
218 fn ensure_ascii_split() {
219 let v = json!({"u": "café 🎉"});
220 assert_eq!(
221 dumps_indent2(&v),
222 "{\n \"u\": \"caf\\u00e9 \\ud83c\\udf89\"\n}"
223 );
224 assert_eq!(dumps_compact(&v), "{\"u\": \"café 🎉\"}");
225 }
226
227 #[test]
228 fn int_vs_float_form() {
229 let v = json!({"i": 2, "f": py_float(2.0), "t": 1e-5});
230 assert_eq!(dumps_compact(&v), "{\"i\": 2, \"f\": 2.0, \"t\": 1e-05}");
231 }
232
233 #[test]
234 fn canonical_sorted_dialect() {
235 let v = json!([{"title": "café — x", "identifier": "A", "category": null}]);
237 assert_eq!(
238 dumps_canonical_sorted(&v),
239 "[{\"category\":null,\"identifier\":\"A\",\"title\":\"café — x\"}]"
240 );
241 }
242}