1use std::collections::HashSet;
4
5use sim_codec_json::JsonTree;
6use sim_kernel::CodecId;
7
8const JSON_CODEC: CodecId = CodecId(0);
9
10#[derive(Clone, Debug, PartialEq)]
13pub enum JavascriptJsonValue {
14 Null,
16 Bool(bool),
18 Number(f64),
20 String(String),
22 Array(Vec<JavascriptJsonValue>),
24 Object(Vec<(String, JavascriptJsonValue)>),
26 Undefined,
28}
29#[derive(Clone, Debug, Eq, PartialEq)]
31pub enum JavascriptJsonError {
32 Parse(String),
34 Cycle,
36 NonFinite,
38}
39pub type JsonReviver<'a> = dyn FnMut(&str, JavascriptJsonValue) -> Option<JavascriptJsonValue> + 'a;
41pub type JsonReplacer<'a> =
43 dyn FnMut(&str, JavascriptJsonValue) -> Option<JavascriptJsonValue> + 'a;
44pub type JsonToJson<'a> = dyn FnMut(&str, &JavascriptJsonValue) -> Option<JavascriptJsonValue> + 'a;
46
47pub fn parse_javascript_json(
49 text: &str,
50 mut reviver: Option<&mut JsonReviver<'_>>,
51) -> Result<JavascriptJsonValue, JavascriptJsonError> {
52 let parsed = sim_codec_json::parse_json(JSON_CODEC, text)
53 .map_err(|error| JavascriptJsonError::Parse(codec_message(error)))?;
54 let value = from_json_tree(parsed)?;
55 Ok(if let Some(callback) = reviver.as_mut() {
56 walk_reviver("", value, *callback).unwrap_or(JavascriptJsonValue::Undefined)
57 } else {
58 value
59 })
60}
61pub fn stringify_javascript_json(
63 value: &JavascriptJsonValue,
64 mut to_json: Option<&mut JsonToJson<'_>>,
65 mut replacer: Option<&mut JsonReplacer<'_>>,
66) -> Result<Option<String>, JavascriptJsonError> {
67 let mut ancestors = HashSet::new();
68 let projected = project(
69 "",
70 value.clone(),
71 &mut to_json,
72 &mut replacer,
73 &mut ancestors,
74 false,
75 )?;
76 match projected {
77 None | Some(JavascriptJsonValue::Undefined) => Ok(None),
78 Some(value) => {
79 let tree = to_json_tree(value, false)?;
80 sim_codec_json::render_json(JSON_CODEC, &tree)
81 .map(Some)
82 .map_err(|error| JavascriptJsonError::Parse(codec_message(error)))
83 }
84 }
85}
86fn from_json_tree(v: JsonTree) -> Result<JavascriptJsonValue, JavascriptJsonError> {
87 match v {
88 JsonTree::Null => Ok(JavascriptJsonValue::Null),
89 JsonTree::Bool(v) => Ok(JavascriptJsonValue::Bool(v)),
90 number @ JsonTree::Number(_) => number
91 .number_as_f64(JSON_CODEC)
92 .map(JavascriptJsonValue::Number)
93 .map_err(|error| JavascriptJsonError::Parse(codec_message(error))),
94 JsonTree::String(v) => Ok(JavascriptJsonValue::String(v)),
95 JsonTree::Array(v) => v
96 .into_iter()
97 .map(from_json_tree)
98 .collect::<Result<_, _>>()
99 .map(JavascriptJsonValue::Array),
100 JsonTree::Object(v) => v
101 .into_iter()
102 .map(|(key, value)| Ok((key, from_json_tree(value)?)))
103 .collect::<Result<_, _>>()
104 .map(JavascriptJsonValue::Object),
105 }
106}
107fn walk_reviver(
108 key: &str,
109 value: JavascriptJsonValue,
110 reviver: &mut JsonReviver<'_>,
111) -> Option<JavascriptJsonValue> {
112 let walked = match value {
113 JavascriptJsonValue::Array(values) => JavascriptJsonValue::Array(
114 values
115 .into_iter()
116 .enumerate()
117 .map(|(i, v)| {
118 walk_reviver(&i.to_string(), v, reviver)
119 .unwrap_or(JavascriptJsonValue::Undefined)
120 })
121 .collect(),
122 ),
123 JavascriptJsonValue::Object(entries) => JavascriptJsonValue::Object(
124 entries
125 .into_iter()
126 .filter_map(|(k, v)| walk_reviver(&k, v, reviver).map(|v| (k, v)))
127 .collect(),
128 ),
129 v => v,
130 };
131 reviver(key, walked)
132}
133fn project(
134 key: &str,
135 mut value: JavascriptJsonValue,
136 to_json: &mut Option<&mut JsonToJson<'_>>,
137 replacer: &mut Option<&mut JsonReplacer<'_>>,
138 ancestors: &mut HashSet<usize>,
139 in_array: bool,
140) -> Result<Option<JavascriptJsonValue>, JavascriptJsonError> {
141 if let Some(hook) = to_json.as_mut()
142 && let Some(v) = hook(key, &value)
143 {
144 value = v;
145 }
146 if let Some(callback) = replacer.as_mut() {
147 let Some(v) = callback(key, value) else {
148 return Ok(None);
149 };
150 value = v;
151 }
152 match value {
153 JavascriptJsonValue::Array(values) => {
154 let identity = values.as_ptr() as usize;
155 if !ancestors.insert(identity) {
156 return Err(JavascriptJsonError::Cycle);
157 }
158 let mut out = Vec::with_capacity(values.len());
159 for (i, v) in values.into_iter().enumerate() {
160 out.push(
161 project(&i.to_string(), v, to_json, replacer, ancestors, true)?
162 .unwrap_or(JavascriptJsonValue::Null),
163 );
164 }
165 ancestors.remove(&identity);
166 Ok(Some(JavascriptJsonValue::Array(out)))
167 }
168 JavascriptJsonValue::Object(entries) => {
169 let identity = entries.as_ptr() as usize;
170 if !ancestors.insert(identity) {
171 return Err(JavascriptJsonError::Cycle);
172 }
173 let mut out = Vec::new();
174 for (k, v) in ordered_entries(entries) {
175 if let Some(v) = project(&k, v, to_json, replacer, ancestors, false)?
176 && !matches!(v, JavascriptJsonValue::Undefined)
177 {
178 out.push((k, v));
179 }
180 }
181 ancestors.remove(&identity);
182 Ok(Some(JavascriptJsonValue::Object(out)))
183 }
184 JavascriptJsonValue::Undefined if in_array => Ok(Some(JavascriptJsonValue::Null)),
185 JavascriptJsonValue::Undefined => Ok(None),
186 v => Ok(Some(v)),
187 }
188}
189fn ordered_entries(
190 entries: Vec<(String, JavascriptJsonValue)>,
191) -> Vec<(String, JavascriptJsonValue)> {
192 let mut indexed = Vec::new();
193 let mut named = Vec::new();
194 for (order, (key, value)) in entries.into_iter().enumerate() {
195 if let Some(index) = array_index(&key) {
196 indexed.push((index, order, key, value));
197 } else {
198 named.push((order, key, value));
199 }
200 }
201 indexed.sort_by_key(|v| v.0);
202 indexed
203 .into_iter()
204 .map(|(_, _, k, v)| (k, v))
205 .chain(named.into_iter().map(|(_, k, v)| (k, v)))
206 .collect()
207}
208fn array_index(key: &str) -> Option<u32> {
209 let value = key.parse::<u32>().ok()?;
210 if value == u32::MAX || value.to_string() != key {
211 return None;
212 }
213 Some(value)
214}
215fn to_json_tree(v: JavascriptJsonValue, in_array: bool) -> Result<JsonTree, JavascriptJsonError> {
216 Ok(match v {
217 JavascriptJsonValue::Null => JsonTree::Null,
218 JavascriptJsonValue::Bool(v) => JsonTree::Bool(v),
219 JavascriptJsonValue::Number(v) => {
220 JsonTree::number_from_f64(JSON_CODEC, v).map_err(|_| JavascriptJsonError::NonFinite)?
221 }
222 JavascriptJsonValue::String(v) => JsonTree::String(v),
223 JavascriptJsonValue::Array(v) => JsonTree::Array(
224 v.into_iter()
225 .map(|v| to_json_tree(v, true))
226 .collect::<Result<_, _>>()?,
227 ),
228 JavascriptJsonValue::Object(v) => {
229 let mut entries: Vec<(String, JsonTree)> = Vec::new();
230 for (k, v) in v {
231 if !matches!(v, JavascriptJsonValue::Undefined) {
232 let value = to_json_tree(v, false)?;
233 if let Some((_, existing)) = entries.iter_mut().find(|(key, _)| key == &k) {
234 *existing = value;
235 } else {
236 entries.push((k, value));
237 }
238 }
239 }
240 JsonTree::Object(entries)
241 }
242 JavascriptJsonValue::Undefined if in_array => JsonTree::Null,
243 JavascriptJsonValue::Undefined => JsonTree::Null,
244 })
245}
246
247fn codec_message(error: sim_kernel::Error) -> String {
248 match error {
249 sim_kernel::Error::CodecError { message, .. } => message,
250 error => error.to_string(),
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257 #[test]
258 fn reviver_is_bottom_up() {
259 let mut keys = Vec::new();
260 let mut r = |key: &str, v| {
261 keys.push(key.to_owned());
262 Some(v)
263 };
264 parse_javascript_json(r#"{"a":[1]}"#, Some(&mut r)).unwrap();
265 assert_eq!(keys, vec!["0", "a", ""]);
266 }
267 #[test]
268 fn replacer_to_json_and_property_order_compose() {
269 let value = JavascriptJsonValue::Object(vec![
270 ("b".into(), JavascriptJsonValue::Number(1.)),
271 ("10".into(), JavascriptJsonValue::Number(10.)),
272 ("2".into(), JavascriptJsonValue::Number(2.)),
273 ]);
274 let mut hook = |_: &str, v: &JavascriptJsonValue| Some(v.clone());
275 let mut replace = |k: &str, v| if k == "b" { None } else { Some(v) };
276 assert_eq!(
277 stringify_javascript_json(&value, Some(&mut hook), Some(&mut replace))
278 .unwrap()
279 .unwrap(),
280 r#"{"2":2.0,"10":10.0}"#
281 );
282 }
283 #[test]
284 fn undefined_policy_matches_arrays_and_objects() {
285 let a = JavascriptJsonValue::Array(vec![JavascriptJsonValue::Undefined]);
286 assert_eq!(
287 stringify_javascript_json(&a, None, None).unwrap(),
288 Some("[null]".into())
289 );
290 assert_eq!(
291 stringify_javascript_json(&JavascriptJsonValue::Undefined, None, None).unwrap(),
292 None
293 );
294 }
295
296 #[test]
297 fn reviver_deletion_distinguishes_objects_arrays_and_root() {
298 let mut reviver = |key: &str, value| (key != "drop").then_some(value);
299 assert_eq!(
300 parse_javascript_json(r#"{"drop":1,"keep":[2]}"#, Some(&mut reviver)).unwrap(),
301 JavascriptJsonValue::Object(vec![(
302 "keep".into(),
303 JavascriptJsonValue::Array(vec![JavascriptJsonValue::Number(2.0)]),
304 )])
305 );
306
307 let mut array_reviver = |key: &str, value| (key != "0").then_some(value);
308 assert_eq!(
309 parse_javascript_json("[1]", Some(&mut array_reviver)).unwrap(),
310 JavascriptJsonValue::Array(vec![JavascriptJsonValue::Undefined])
311 );
312
313 let mut root_reviver = |key: &str, value| (!key.is_empty()).then_some(value);
314 assert_eq!(
315 parse_javascript_json("null", Some(&mut root_reviver)).unwrap(),
316 JavascriptJsonValue::Undefined
317 );
318 }
319
320 #[test]
321 fn to_json_precedes_replacer_at_every_level() {
322 use std::cell::RefCell;
323
324 let value = JavascriptJsonValue::Object(vec![(
325 "item".into(),
326 JavascriptJsonValue::String("before".into()),
327 )]);
328 let events = RefCell::new(Vec::new());
329 let mut hook = |key: &str, value: &JavascriptJsonValue| {
330 events.borrow_mut().push(format!("toJSON:{key}"));
331 Some(value.clone())
332 };
333 let mut replacer = |key: &str, value| {
334 events.borrow_mut().push(format!("replacer:{key}"));
335 Some(value)
336 };
337 assert_eq!(
338 stringify_javascript_json(&value, Some(&mut hook), Some(&mut replacer)).unwrap(),
339 Some(r#"{"item":"before"}"#.into())
340 );
341 assert_eq!(
342 events.into_inner(),
343 ["toJSON:", "replacer:", "toJSON:item", "replacer:item"]
344 );
345 }
346
347 #[test]
348 fn replacer_omits_object_members_nulls_array_cells_and_omits_root() {
349 let value = JavascriptJsonValue::Object(vec![
350 ("gone".into(), JavascriptJsonValue::Bool(true)),
351 (
352 "array".into(),
353 JavascriptJsonValue::Array(vec![JavascriptJsonValue::Bool(true)]),
354 ),
355 ]);
356 let mut replacer = |key: &str, value| (key != "gone" && key != "0").then_some(value);
357 assert_eq!(
358 stringify_javascript_json(&value, None, Some(&mut replacer)).unwrap(),
359 Some(r#"{"array":[null]}"#.into())
360 );
361 let mut root_replacer = |key: &str, value| (!key.is_empty()).then_some(value);
362 assert_eq!(
363 stringify_javascript_json(&value, None, Some(&mut root_replacer)).unwrap(),
364 None
365 );
366 }
367
368 #[test]
369 fn number_failures_duplicates_order_diagnostics_and_exact_text_are_stable() {
370 for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
371 assert_eq!(
372 stringify_javascript_json(&JavascriptJsonValue::Number(value), None, None),
373 Err(JavascriptJsonError::NonFinite)
374 );
375 }
376 let value = JavascriptJsonValue::Object(vec![
377 ("4294967295".into(), JavascriptJsonValue::Number(1.0)),
378 ("01".into(), JavascriptJsonValue::Number(2.0)),
379 ("2".into(), JavascriptJsonValue::Number(3.0)),
380 ("1".into(), JavascriptJsonValue::Number(4.0)),
381 ("01".into(), JavascriptJsonValue::Number(5.0)),
382 ]);
383 assert_eq!(
384 stringify_javascript_json(&value, None, None).unwrap(),
385 Some(r#"{"1":4.0,"2":3.0,"4294967295":1.0,"01":5.0}"#.into())
386 );
387 assert!(matches!(
388 parse_javascript_json("{]", None),
389 Err(JavascriptJsonError::Parse(message)) if message.contains("line 1 column 2")
390 ));
391 }
392}