1use alloc::string::String;
2use alloc::vec::Vec;
3use core::fmt::Write;
4
5use crate::{EncodeError, JsonValue};
6
7pub fn encode(value: &JsonValue) -> Result<Vec<u8>, EncodeError> {
9 let mut output = Vec::new();
10 encode_value(value, &mut output)?;
11 Ok(output)
12}
13
14fn encode_value(value: &JsonValue, output: &mut Vec<u8>) -> Result<(), EncodeError> {
15 match value {
16 JsonValue::Null => output.extend_from_slice(b"null"),
17 JsonValue::Bool(true) => output.extend_from_slice(b"true"),
18 JsonValue::Bool(false) => output.extend_from_slice(b"false"),
19 JsonValue::Number(number) => encode_number(*number, output)?,
20 JsonValue::String(text) => encode_string(text, output),
21 JsonValue::Array(items) => {
22 output.push(b'[');
23 for (index, item) in items.iter().enumerate() {
24 if index > 0 {
25 output.push(b',');
26 }
27 encode_value(item, output)?;
28 }
29 output.push(b']');
30 }
31 JsonValue::Object(fields) => {
32 output.push(b'{');
33 for (index, (key, value)) in fields.iter().enumerate() {
34 if index > 0 {
35 output.push(b',');
36 }
37 encode_string(key, output);
38 output.push(b':');
39 encode_value(value, output)?;
40 }
41 output.push(b'}');
42 }
43 }
44 Ok(())
45}
46
47fn encode_number(number: f64, output: &mut Vec<u8>) -> Result<(), EncodeError> {
48 if !number.is_finite() {
49 return Err(EncodeError::NonFiniteNumber);
50 }
51
52 let mut buffer = String::new();
53 write!(&mut buffer, "{number}").expect("writing to String should not fail");
54 output.extend_from_slice(buffer.as_bytes());
55 Ok(())
56}
57
58fn encode_string(text: &str, output: &mut Vec<u8>) {
59 output.push(b'"');
60 for ch in text.chars() {
61 match ch {
62 '"' => output.extend_from_slice(br#"\""#),
63 '\\' => output.extend_from_slice(br#"\\"#),
64 '\u{0008}' => output.extend_from_slice(br#"\b"#),
65 '\u{000C}' => output.extend_from_slice(br#"\f"#),
66 '\n' => output.extend_from_slice(br#"\n"#),
67 '\r' => output.extend_from_slice(br#"\r"#),
68 '\t' => output.extend_from_slice(br#"\t"#),
69 '\u{0000}'..='\u{001F}' => encode_control_escape(ch as u32, output),
70 _ => {
71 let mut buffer = [0_u8; 4];
72 let encoded = ch.encode_utf8(&mut buffer);
73 output.extend_from_slice(encoded.as_bytes());
74 }
75 }
76 }
77 output.push(b'"');
78}
79
80fn encode_control_escape(code: u32, output: &mut Vec<u8>) {
81 output.extend_from_slice(br#"\u00"#);
82 output.push(hex_digit(((code >> 4) & 0x0F) as u8));
83 output.push(hex_digit((code & 0x0F) as u8));
84}
85
86fn hex_digit(value: u8) -> u8 {
87 match value {
88 0..=9 => b'0' + value,
89 10..=15 => b'A' + (value - 10),
90 _ => unreachable!("hex digit is always in range"),
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use alloc::vec;
97
98 use super::encode;
99 use crate::{parse, EncodeError, JsonValue};
100
101 #[test]
102 fn encodes_basic_variants() {
103 assert_eq!(encode(&JsonValue::Null).unwrap(), b"null");
104 assert_eq!(encode(&JsonValue::Bool(true)).unwrap(), b"true");
105 assert_eq!(encode(&JsonValue::Bool(false)).unwrap(), b"false");
106 assert_eq!(encode(&JsonValue::Number(42.5)).unwrap(), b"42.5");
107 assert_eq!(
108 encode(&JsonValue::String("neco".into())).unwrap(),
109 b"\"neco\""
110 );
111 assert_eq!(encode(&JsonValue::Array(vec![])).unwrap(), b"[]");
112 assert_eq!(encode(&JsonValue::Object(vec![])).unwrap(), b"{}");
113 }
114
115 #[test]
116 fn rejects_non_finite_numbers() {
117 assert_eq!(
118 encode(&JsonValue::Number(f64::NAN)),
119 Err(EncodeError::NonFiniteNumber)
120 );
121 assert_eq!(
122 encode(&JsonValue::Number(f64::INFINITY)),
123 Err(EncodeError::NonFiniteNumber)
124 );
125 assert_eq!(
126 encode(&JsonValue::Number(f64::NEG_INFINITY)),
127 Err(EncodeError::NonFiniteNumber)
128 );
129 }
130
131 #[test]
132 fn escapes_strings() {
133 let value = JsonValue::String("quote:\" slash:\\\n\r\t".into());
134 assert_eq!(encode(&value).unwrap(), br#""quote:\" slash:\\\n\r\t""#);
135
136 let control = JsonValue::String("\u{0000}\u{0008}\u{000C}\u{001f}".into());
137 assert_eq!(encode(&control).unwrap(), br#""\u0000\b\f\u001F""#);
138 }
139
140 #[test]
141 fn encodes_integer_valued_f64_using_current_representation() {
142 assert_eq!(encode(&JsonValue::Number(42.0)).unwrap(), b"42");
143 assert_eq!(encode(&JsonValue::Number(-0.0)).unwrap(), b"-0");
144 }
145
146 #[test]
147 fn escapes_null_character_in_string() {
148 let value = JsonValue::String("a\u{0000}b".into());
149 assert_eq!(encode(&value).unwrap(), br#""a\u0000b""#);
150 }
151
152 #[test]
153 fn encodes_nested_structures() {
154 let value = JsonValue::Object(vec![
155 ("name".into(), JsonValue::String("neco".into())),
156 (
157 "items".into(),
158 JsonValue::Array(vec![
159 JsonValue::Null,
160 JsonValue::Bool(true),
161 JsonValue::Object(vec![("x".into(), JsonValue::Number(1.0))]),
162 ]),
163 ),
164 ]);
165
166 assert_eq!(
167 encode(&value).unwrap(),
168 br#"{"name":"neco","items":[null,true,{"x":1}]}"#
169 );
170 }
171
172 #[test]
173 fn encode_parse_roundtrip_for_finite_values() {
174 let value = JsonValue::Object(vec![
175 ("null".into(), JsonValue::Null),
176 ("bool".into(), JsonValue::Bool(true)),
177 ("int".into(), JsonValue::Number(7.0)),
178 ("float".into(), JsonValue::Number(-3.25)),
179 ("text".into(), JsonValue::String("ねこ\njson".into())),
180 (
181 "array".into(),
182 JsonValue::Array(vec![
183 JsonValue::Number(0.5),
184 JsonValue::String("\"quoted\"".into()),
185 ]),
186 ),
187 (
188 "object".into(),
189 JsonValue::Object(vec![("nested".into(), JsonValue::Bool(false))]),
190 ),
191 ]);
192
193 let encoded = encode(&value).unwrap();
194 let decoded = parse(&encoded).expect("encoded bytes should parse");
195 assert_eq!(decoded, value);
196 }
197
198 #[test]
199 fn encode_parse_roundtrip_for_complex_nested_structure() {
200 let value = JsonValue::Object(vec![
201 (
202 "meta".into(),
203 JsonValue::Object(vec![
204 ("version".into(), JsonValue::Number(1.0)),
205 (
206 "tags".into(),
207 JsonValue::Array(vec![
208 JsonValue::String("alpha".into()),
209 JsonValue::String("beta".into()),
210 JsonValue::Null,
211 ]),
212 ),
213 ]),
214 ),
215 (
216 "items".into(),
217 JsonValue::Array(vec![
218 JsonValue::Object(vec![
219 ("id".into(), JsonValue::String("one".into())),
220 ("active".into(), JsonValue::Bool(true)),
221 ("score".into(), JsonValue::Number(1.5e2)),
222 ]),
223 JsonValue::Array(vec![
224 JsonValue::String("nested".into()),
225 JsonValue::Object(vec![(
226 "control".into(),
227 JsonValue::String("x\u{0000}y".into()),
228 )]),
229 ]),
230 ]),
231 ),
232 ("empty".into(), JsonValue::Object(vec![])),
233 ]);
234
235 let encoded = encode(&value).unwrap();
236 let decoded = parse(&encoded).expect("encoded bytes should parse");
237 assert_eq!(decoded, value);
238 }
239}