1use std::collections::HashSet;
4
5#[derive(Clone, Debug, PartialEq)]
8pub enum JavascriptJsonValue {
9 Null,
11 Bool(bool),
13 Number(f64),
15 String(String),
17 Array(Vec<JavascriptJsonValue>),
19 Object(Vec<(String, JavascriptJsonValue)>),
21 Undefined,
23}
24#[derive(Clone, Debug, Eq, PartialEq)]
26pub enum JavascriptJsonError {
27 Parse(String),
29 Cycle,
31 NonFinite,
33}
34pub type JsonReviver<'a> = dyn FnMut(&str, JavascriptJsonValue) -> Option<JavascriptJsonValue> + 'a;
36pub type JsonReplacer<'a> =
38 dyn FnMut(&str, JavascriptJsonValue) -> Option<JavascriptJsonValue> + 'a;
39pub type JsonToJson<'a> = dyn FnMut(&str, &JavascriptJsonValue) -> Option<JavascriptJsonValue> + 'a;
41
42pub 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}
64pub 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}