Skip to main content

sim_lib_lang_javascript/
json.rs

1//! ECMAScript JSON policy over the canonical JSON representation.
2
3use std::collections::HashSet;
4
5/// JavaScript JSON-domain value. Objects retain insertion order until the
6/// ECMAScript property-order projection is applied.
7#[derive(Clone, Debug, PartialEq)]
8pub enum JavascriptJsonValue {
9    /// JSON null.
10    Null,
11    /// Boolean.
12    Bool(bool),
13    /// Number.
14    Number(f64),
15    /// String.
16    String(String),
17    /// Array.
18    Array(Vec<JavascriptJsonValue>),
19    /// Object as insertion-ordered own string properties.
20    Object(Vec<(String, JavascriptJsonValue)>),
21    /// Value omitted by object serialization or rendered as null in arrays.
22    Undefined,
23}
24/// JSON policy error.
25#[derive(Clone, Debug, Eq, PartialEq)]
26pub enum JavascriptJsonError {
27    /// Canonical parser rejected the input.
28    Parse(String),
29    /// A cycle was found during stringify.
30    Cycle,
31    /// Number is not representable by JSON.
32    NonFinite,
33}
34/// Reviver callback, invoked bottom-up with the empty root key last.
35pub type JsonReviver<'a> = dyn FnMut(&str, JavascriptJsonValue) -> Option<JavascriptJsonValue> + 'a;
36/// Replacer callback, invoked top-down with the empty root key first.
37pub type JsonReplacer<'a> =
38    dyn FnMut(&str, JavascriptJsonValue) -> Option<JavascriptJsonValue> + 'a;
39/// JavaScript `toJSON` hook invoked before the replacer.
40pub type JsonToJson<'a> = dyn FnMut(&str, &JavascriptJsonValue) -> Option<JavascriptJsonValue> + 'a;
41
42/// Parse through the canonical JSON parser, then apply ECMAScript reviver order.
43pub fn parse_javascript_json(
44    text: &str,
45    mut reviver: Option<&mut JsonReviver<'_>>,
46) -> Result<JavascriptJsonValue, JavascriptJsonError> {
47    let parsed: serde_json::Value =
48        serde_json::from_str(text).map_err(|e| JavascriptJsonError::Parse(e.to_string()))?;
49    let expr = sim_codec_json::project_json_to_expr(
50        &parsed,
51        sim_codec_json::JsonProjectionMode::UntaggedInterop,
52    );
53    let canonical = sim_codec_json::project_expr_to_json(
54        &expr,
55        sim_codec_json::JsonProjectionMode::UntaggedInterop,
56    );
57    let value = from_canonical(canonical);
58    Ok(if let Some(callback) = reviver.as_mut() {
59        walk_reviver("", value, *callback).unwrap_or(JavascriptJsonValue::Undefined)
60    } else {
61        value
62    })
63}
64/// Serialize with `toJSON`, replacer, ECMAScript own-property order, and cycle rejection.
65pub fn stringify_javascript_json(
66    value: &JavascriptJsonValue,
67    mut to_json: Option<&mut JsonToJson<'_>>,
68    mut replacer: Option<&mut JsonReplacer<'_>>,
69) -> Result<Option<String>, JavascriptJsonError> {
70    let mut ancestors = HashSet::new();
71    let projected = project(
72        "",
73        value.clone(),
74        &mut to_json,
75        &mut replacer,
76        &mut ancestors,
77        false,
78    )?;
79    match projected {
80        None | Some(JavascriptJsonValue::Undefined) => Ok(None),
81        Some(value) => {
82            let json = to_canonical(value, false)?;
83            let expr = sim_codec_json::project_json_to_expr(
84                &json,
85                sim_codec_json::JsonProjectionMode::UntaggedInterop,
86            );
87            let canonical = sim_codec_json::project_expr_to_json(
88                &expr,
89                sim_codec_json::JsonProjectionMode::UntaggedInterop,
90            );
91            serde_json::to_string(&canonical)
92                .map(Some)
93                .map_err(|e| JavascriptJsonError::Parse(e.to_string()))
94        }
95    }
96}
97fn from_canonical(v: serde_json::Value) -> JavascriptJsonValue {
98    match v {
99        serde_json::Value::Null => JavascriptJsonValue::Null,
100        serde_json::Value::Bool(v) => JavascriptJsonValue::Bool(v),
101        serde_json::Value::Number(v) => JavascriptJsonValue::Number(v.as_f64().unwrap_or(f64::NAN)),
102        serde_json::Value::String(v) => JavascriptJsonValue::String(v),
103        serde_json::Value::Array(v) => {
104            JavascriptJsonValue::Array(v.into_iter().map(from_canonical).collect())
105        }
106        serde_json::Value::Object(v) => JavascriptJsonValue::Object(
107            v.into_iter().map(|(k, v)| (k, from_canonical(v))).collect(),
108        ),
109    }
110}
111fn walk_reviver(
112    key: &str,
113    value: JavascriptJsonValue,
114    reviver: &mut JsonReviver<'_>,
115) -> Option<JavascriptJsonValue> {
116    let walked = match value {
117        JavascriptJsonValue::Array(values) => JavascriptJsonValue::Array(
118            values
119                .into_iter()
120                .enumerate()
121                .map(|(i, v)| {
122                    walk_reviver(&i.to_string(), v, reviver)
123                        .unwrap_or(JavascriptJsonValue::Undefined)
124                })
125                .collect(),
126        ),
127        JavascriptJsonValue::Object(entries) => JavascriptJsonValue::Object(
128            entries
129                .into_iter()
130                .filter_map(|(k, v)| walk_reviver(&k, v, reviver).map(|v| (k, v)))
131                .collect(),
132        ),
133        v => v,
134    };
135    reviver(key, walked)
136}
137fn project(
138    key: &str,
139    mut value: JavascriptJsonValue,
140    to_json: &mut Option<&mut JsonToJson<'_>>,
141    replacer: &mut Option<&mut JsonReplacer<'_>>,
142    ancestors: &mut HashSet<usize>,
143    in_array: bool,
144) -> Result<Option<JavascriptJsonValue>, JavascriptJsonError> {
145    if let Some(hook) = to_json.as_mut()
146        && let Some(v) = hook(key, &value)
147    {
148        value = v;
149    }
150    if let Some(callback) = replacer.as_mut() {
151        let Some(v) = callback(key, value) else {
152            return Ok(None);
153        };
154        value = v;
155    }
156    match value {
157        JavascriptJsonValue::Array(values) => {
158            let identity = values.as_ptr() as usize;
159            if !ancestors.insert(identity) {
160                return Err(JavascriptJsonError::Cycle);
161            }
162            let mut out = Vec::with_capacity(values.len());
163            for (i, v) in values.into_iter().enumerate() {
164                out.push(
165                    project(&i.to_string(), v, to_json, replacer, ancestors, true)?
166                        .unwrap_or(JavascriptJsonValue::Null),
167                );
168            }
169            ancestors.remove(&identity);
170            Ok(Some(JavascriptJsonValue::Array(out)))
171        }
172        JavascriptJsonValue::Object(entries) => {
173            let identity = entries.as_ptr() as usize;
174            if !ancestors.insert(identity) {
175                return Err(JavascriptJsonError::Cycle);
176            }
177            let mut out = Vec::new();
178            for (k, v) in ordered_entries(entries) {
179                if let Some(v) = project(&k, v, to_json, replacer, ancestors, false)?
180                    && !matches!(v, JavascriptJsonValue::Undefined)
181                {
182                    out.push((k, v));
183                }
184            }
185            ancestors.remove(&identity);
186            Ok(Some(JavascriptJsonValue::Object(out)))
187        }
188        JavascriptJsonValue::Undefined if in_array => Ok(Some(JavascriptJsonValue::Null)),
189        JavascriptJsonValue::Undefined => Ok(None),
190        v => Ok(Some(v)),
191    }
192}
193fn ordered_entries(
194    entries: Vec<(String, JavascriptJsonValue)>,
195) -> Vec<(String, JavascriptJsonValue)> {
196    let mut indexed = Vec::new();
197    let mut named = Vec::new();
198    for (order, (key, value)) in entries.into_iter().enumerate() {
199        if let Some(index) = array_index(&key) {
200            indexed.push((index, order, key, value));
201        } else {
202            named.push((order, key, value));
203        }
204    }
205    indexed.sort_by_key(|v| v.0);
206    indexed
207        .into_iter()
208        .map(|(_, _, k, v)| (k, v))
209        .chain(named.into_iter().map(|(_, k, v)| (k, v)))
210        .collect()
211}
212fn array_index(key: &str) -> Option<u32> {
213    let value = key.parse::<u32>().ok()?;
214    if value == u32::MAX || value.to_string() != key {
215        return None;
216    }
217    Some(value)
218}
219fn to_canonical(
220    v: JavascriptJsonValue,
221    in_array: bool,
222) -> Result<serde_json::Value, JavascriptJsonError> {
223    Ok(match v {
224        JavascriptJsonValue::Null => serde_json::Value::Null,
225        JavascriptJsonValue::Bool(v) => serde_json::Value::Bool(v),
226        JavascriptJsonValue::Number(v) => serde_json::Number::from_f64(v)
227            .map(serde_json::Value::Number)
228            .ok_or(JavascriptJsonError::NonFinite)?,
229        JavascriptJsonValue::String(v) => serde_json::Value::String(v),
230        JavascriptJsonValue::Array(v) => serde_json::Value::Array(
231            v.into_iter()
232                .map(|v| to_canonical(v, true))
233                .collect::<Result<_, _>>()?,
234        ),
235        JavascriptJsonValue::Object(v) => {
236            let mut map = serde_json::Map::new();
237            for (k, v) in v {
238                if !matches!(v, JavascriptJsonValue::Undefined) {
239                    map.insert(k, to_canonical(v, false)?);
240                }
241            }
242            serde_json::Value::Object(map)
243        }
244        JavascriptJsonValue::Undefined if in_array => serde_json::Value::Null,
245        JavascriptJsonValue::Undefined => serde_json::Value::Null,
246    })
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252    #[test]
253    fn reviver_is_bottom_up() {
254        let mut keys = Vec::new();
255        let mut r = |key: &str, v| {
256            keys.push(key.to_owned());
257            Some(v)
258        };
259        parse_javascript_json(r#"{"a":[1]}"#, Some(&mut r)).unwrap();
260        assert_eq!(keys, vec!["0", "a", ""]);
261    }
262    #[test]
263    fn replacer_to_json_and_property_order_compose() {
264        let value = JavascriptJsonValue::Object(vec![
265            ("b".into(), JavascriptJsonValue::Number(1.)),
266            ("10".into(), JavascriptJsonValue::Number(10.)),
267            ("2".into(), JavascriptJsonValue::Number(2.)),
268        ]);
269        let mut hook = |_: &str, v: &JavascriptJsonValue| Some(v.clone());
270        let mut replace = |k: &str, v| if k == "b" { None } else { Some(v) };
271        assert_eq!(
272            stringify_javascript_json(&value, Some(&mut hook), Some(&mut replace))
273                .unwrap()
274                .unwrap(),
275            r#"{"2":2.0,"10":10.0}"#
276        );
277    }
278    #[test]
279    fn undefined_policy_matches_arrays_and_objects() {
280        let a = JavascriptJsonValue::Array(vec![JavascriptJsonValue::Undefined]);
281        assert_eq!(
282            stringify_javascript_json(&a, None, None).unwrap(),
283            Some("[null]".into())
284        );
285        assert_eq!(
286            stringify_javascript_json(&JavascriptJsonValue::Undefined, None, None).unwrap(),
287            None
288        );
289    }
290}