1use indexmap::IndexMap;
7use serde::{
8 Deserialize, Deserializer, Serialize, Serializer,
9 de::{DeserializeOwned, IntoDeserializer, MapAccess, SeqAccess, Visitor},
10};
11use serde_json::{Number, Value};
12use std::{
13 any::{Any, TypeId},
14 fmt,
15};
16
17#[derive(Clone, Debug, PartialEq)]
18pub enum JsValue {
19 Null,
20 Bool(bool),
21 Number(f64),
22 String(String),
23 Date(chrono::DateTime<chrono::Utc>),
25 Array(Vec<Self>),
26 Object(IndexMap<String, Self>),
27}
28
29impl JsValue {
30 pub fn coerce_string(&self) -> Result<String, &'static str> {
34 match self {
35 Self::Null => Ok("null".to_owned()),
36 Self::Bool(value) => Ok(value.to_string()),
37 Self::String(value) => Ok(value.clone()),
38 Self::Date(value) => Ok(value
39 .format("%a %b %d %Y %H:%M:%S GMT%z (Coordinated Universal Time)")
40 .to_string()),
41 Self::Number(value) => Ok(ryu_js::Buffer::new().format(*value).to_owned()),
42 Self::Array(values) => values
43 .iter()
44 .map(|value| match value {
45 Self::Null => Ok(String::new()),
46 value => value.coerce_string(),
47 })
48 .collect::<Result<Vec<_>, _>>()
49 .map(|values| values.join(",")),
50 Self::Object(value) if value.contains_key("toString") => {
51 Err("Cannot convert object to primitive value")
52 }
53 Self::Object(_) => Ok("[object Object]".to_owned()),
54 }
55 }
56
57 #[must_use]
58 pub const fn as_f64(&self) -> Option<f64> {
59 if let Self::Number(n) = self {
60 Some(*n)
61 } else {
62 None
63 }
64 }
65 #[must_use]
66 pub fn as_str(&self) -> Option<&str> {
67 if let Self::String(s) = self {
68 Some(s)
69 } else {
70 None
71 }
72 }
73 #[must_use]
74 pub const fn as_date(&self) -> Option<&chrono::DateTime<chrono::Utc>> {
75 if let Self::Date(value) = self {
76 Some(value)
77 } else {
78 None
79 }
80 }
81 #[must_use]
82 pub const fn as_bool(&self) -> Option<bool> {
83 if let Self::Bool(b) = self {
84 Some(*b)
85 } else {
86 None
87 }
88 }
89 #[must_use]
90 pub const fn as_object(&self) -> Option<&IndexMap<String, Self>> {
91 if let Self::Object(m) = self {
92 Some(m)
93 } else {
94 None
95 }
96 }
97 #[must_use]
98 pub fn as_array(&self) -> Option<&[Self]> {
99 if let Self::Array(a) = self {
100 Some(a)
101 } else {
102 None
103 }
104 }
105 #[must_use]
106 pub fn get(&self, key: &str) -> Option<&Self> {
107 self.as_object()?.get(key)
108 }
109 #[must_use]
111 pub fn is_truthy(&self) -> bool {
112 match self {
113 Self::Null => false,
114 Self::Bool(value) => *value,
115 Self::Number(value) => *value != 0.0 && !value.is_nan(),
116 Self::String(value) => !value.is_empty(),
117 Self::Date(_) | Self::Array(_) | Self::Object(_) => true,
118 }
119 }
120 #[must_use]
121 pub const fn is_null(&self) -> bool {
122 matches!(self, Self::Null)
123 }
124 #[must_use]
125 pub const fn is_string(&self) -> bool {
126 matches!(self, Self::String(_))
127 }
128 #[must_use]
129 pub const fn is_boolean(&self) -> bool {
130 matches!(self, Self::Bool(_))
131 }
132 #[must_use]
133 pub const fn is_array(&self) -> bool {
134 matches!(self, Self::Array(_))
135 }
136 #[must_use]
137 pub const fn is_object(&self) -> bool {
138 matches!(self, Self::Object(_))
139 }
140 pub fn to_json_value(&self) -> Result<Value, serde_json::Error> {
146 finite_value(self)
147 }
148}
149
150impl From<Value> for JsValue {
151 fn from(value: Value) -> Self {
152 match value {
153 Value::Null => Self::Null,
154 Value::Bool(b) => Self::Bool(b),
155 Value::Number(n) => Self::Number(n.as_f64().unwrap_or_default()),
156 Value::String(s) => Self::String(s),
157 Value::Array(a) => Self::Array(a.into_iter().map(Self::from).collect()),
158 Value::Object(m) => {
159 Self::Object(m.into_iter().map(|(k, v)| (k, Self::from(v))).collect())
160 }
161 }
162 }
163}
164
165impl Serialize for JsValue {
166 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
167 match self {
168 Self::Null => serializer.serialize_unit(),
169 Self::Bool(b) => serializer.serialize_bool(*b),
170 Self::Number(n) => serializer.serialize_f64(*n),
171 Self::String(s) => serializer.serialize_str(s),
172 Self::Date(value) => serializer
173 .serialize_str(&value.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)),
174 Self::Array(a) => a.serialize(serializer),
175 Self::Object(m) => m.serialize(serializer),
176 }
177 }
178}
179
180impl<'de> Deserialize<'de> for JsValue {
181 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
182 struct JsVisitor;
183 impl<'de> Visitor<'de> for JsVisitor {
184 type Value = JsValue;
185 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
186 formatter.write_str("a JSON value")
187 }
188 fn visit_unit<E>(self) -> Result<Self::Value, E> {
189 Ok(JsValue::Null)
190 }
191 fn visit_none<E>(self) -> Result<Self::Value, E> {
192 Ok(JsValue::Null)
193 }
194 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> {
195 Ok(JsValue::Bool(v))
196 }
197 #[expect(
198 clippy::as_conversions,
199 clippy::cast_precision_loss,
200 reason = "JavaScript-compatible numbers deliberately retain IEEE754 rounding and guarded integer coercion at the wire or adapter boundary"
201 )]
202 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> {
203 Ok(JsValue::Number(v as f64))
204 }
205 #[expect(
206 clippy::as_conversions,
207 clippy::cast_precision_loss,
208 reason = "JavaScript-compatible numbers deliberately retain IEEE754 rounding and guarded integer coercion at the wire or adapter boundary"
209 )]
210 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> {
211 Ok(JsValue::Number(v as f64))
212 }
213 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E> {
214 Ok(JsValue::Number(v))
215 }
216 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
217 Ok(JsValue::String(v.into()))
218 }
219 fn visit_string<E>(self, v: String) -> Result<Self::Value, E> {
220 Ok(JsValue::String(v))
221 }
222 fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
223 let mut values = Vec::new();
224 while let Some(value) = seq.next_element()? {
225 values.push(value);
226 }
227 Ok(JsValue::Array(values))
228 }
229 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
230 let mut values = IndexMap::new();
231 while let Some((key, value)) = map.next_entry()? {
232 _ = values.insert(key, value);
233 }
234 Ok(JsValue::Object(values))
235 }
236 }
237 deserializer.deserialize_any(JsVisitor)
238 }
239}
240
241impl IntoDeserializer<'_, serde_json::Error> for JsValue {
242 type Deserializer = Self;
243 fn into_deserializer(self) -> Self {
244 self
245 }
246}
247
248macro_rules! integer_number {
251 ($visit:ident, $cast:ty, $range:expr, $expect:meta, $($method:ident),*) => {$(
252 #[$expect]
253 fn $method<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
254 match self {
255 Self::Number(n) if n.is_finite() && n.fract() == 0.0 && $range.contains(&n) => {
256 visitor.$visit(n as $cast)
257 }
258 value => value.deserialize_any(visitor),
259 }
260 }
261 )*};
262}
263
264impl<'de> Deserializer<'de> for JsValue {
265 type Error = serde_json::Error;
266 fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
267 match self {
268 Self::Null => visitor.visit_unit(),
269 Self::Bool(b) => visitor.visit_bool(b),
270 Self::Number(n) => visitor.visit_f64(n),
271 Self::String(s) => visitor.visit_string(s),
272 Self::Date(value) => {
273 visitor.visit_string(value.to_rfc3339_opts(chrono::SecondsFormat::Millis, true))
274 }
275 Self::Array(a) => {
276 visitor.visit_seq(serde::de::value::SeqDeserializer::new(a.into_iter()))
277 }
278 Self::Object(m) => {
279 visitor.visit_map(serde::de::value::MapDeserializer::new(m.into_iter()))
280 }
281 }
282 }
283 fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
284 if self.is_null() {
285 visitor.visit_none()
286 } else {
287 visitor.visit_some(self)
288 }
289 }
290 fn deserialize_newtype_struct<V: Visitor<'de>>(
291 self,
292 _name: &'static str,
293 visitor: V,
294 ) -> Result<V::Value, Self::Error> {
295 visitor.visit_newtype_struct(self)
296 }
297 fn deserialize_enum<V: Visitor<'de>>(
298 self,
299 _name: &'static str,
300 _variants: &'static [&'static str],
301 visitor: V,
302 ) -> Result<V::Value, Self::Error> {
303 match self {
304 Self::String(s) => visitor.visit_enum(s.into_deserializer()),
305 Self::Object(map) => visitor.visit_enum(serde::de::value::MapAccessDeserializer::new(
306 serde::de::value::MapDeserializer::new(map.into_iter()),
307 )),
308 value @ (Self::Null
309 | Self::Bool(_)
310 | Self::Number(_)
311 | Self::Date(_)
312 | Self::Array(_)) => value.deserialize_any(visitor),
313 }
314 }
315 integer_number!(
316 visit_i64,
317 i64,
318 (-9_223_372_036_854_776_000.0..9_223_372_036_854_776_000.0),
319 expect(
320 clippy::as_conversions,
321 clippy::cast_possible_truncation,
322 reason = "Finite integral values are checked against the target integer bounds before conversion"
323 ),
324 deserialize_i8,
325 deserialize_i16,
326 deserialize_i32,
327 deserialize_i64,
328 deserialize_i128
329 );
330 integer_number!(
331 visit_u64,
332 u64,
333 (0.0..18_446_744_073_709_552_000.0),
334 expect(
335 clippy::as_conversions,
336 clippy::cast_possible_truncation,
337 clippy::cast_sign_loss,
338 reason = "Finite integral values are checked against the target integer bounds before conversion"
339 ),
340 deserialize_u8,
341 deserialize_u16,
342 deserialize_u32,
343 deserialize_u64,
344 deserialize_u128
345 );
346 serde::forward_to_deserialize_any! {
347 bool f32 f64 char str string bytes
348 byte_buf unit unit_struct seq tuple tuple_struct map struct identifier ignored_any
349 }
350}
351
352struct Parser<'a> {
353 input: &'a str,
354 position: usize,
355}
356
357impl Parser<'_> {
358 fn peek(&self) -> Option<u8> {
359 self.input.as_bytes().get(self.position).copied()
360 }
361 fn whitespace(&mut self) {
362 while self
363 .peek()
364 .is_some_and(|b| matches!(b, b' ' | b'\n' | b'\r' | b'\t'))
365 {
366 self.position += 1;
367 }
368 }
369 fn take(&mut self, byte: u8) -> bool {
370 if self.peek() == Some(byte) {
371 self.position += 1;
372 true
373 } else {
374 false
375 }
376 }
377 fn string(&mut self) -> Result<String, serde_json::Error> {
378 let start = self.position;
379 if !self.take(b'"') {
380 return Err(invalid("expected JSON string"));
381 }
382 loop {
383 match self.peek() {
384 Some(b'"') => {
385 self.position += 1;
386 return serde_json::from_str(
387 self.input
388 .get(start..self.position)
389 .ok_or_else(|| invalid("invalid JSON string bounds"))?,
390 );
391 }
392 Some(b'\\') => {
393 self.position += 1;
394 if self.peek().is_none() {
395 return Err(invalid("unterminated JSON escape"));
396 }
397 self.position += 1;
398 }
399 Some(_) => self.position += 1,
400 None => return Err(invalid("unterminated JSON string")),
401 }
402 }
403 }
404 fn value(&mut self, depth: usize) -> Result<JsValue, serde_json::Error> {
405 self.whitespace();
406 match self.peek() {
407 Some(b'"') => self.string().map(JsValue::String),
408 Some(b'{') => {
409 if depth >= 127 {
410 return Err(invalid("recursion limit exceeded"));
411 }
412 self.position += 1;
413 self.whitespace();
414 let mut values = IndexMap::new();
415 if self.take(b'}') {
416 return Ok(JsValue::Object(values));
417 }
418 loop {
419 self.whitespace();
420 let key = self.string()?;
421 self.whitespace();
422 if !self.take(b':') {
423 return Err(invalid("expected JSON colon"));
424 }
425 let value = self.value(depth + 1)?;
426 _ = values.insert(key, value);
427 self.whitespace();
428 if self.take(b'}') {
429 break;
430 }
431 if !self.take(b',') {
432 return Err(invalid("expected JSON object delimiter"));
433 }
434 }
435 Ok(JsValue::Object(values))
436 }
437 Some(b'[') => {
438 if depth >= 127 {
439 return Err(invalid("recursion limit exceeded"));
440 }
441 self.position += 1;
442 self.whitespace();
443 let mut values = Vec::new();
444 if self.take(b']') {
445 return Ok(JsValue::Array(values));
446 }
447 loop {
448 values.push(self.value(depth + 1)?);
449 self.whitespace();
450 if self.take(b']') {
451 break;
452 }
453 if !self.take(b',') {
454 return Err(invalid("expected JSON array delimiter"));
455 }
456 }
457 Ok(JsValue::Array(values))
458 }
459 Some(b't') => self.literal("true", JsValue::Bool(true)),
460 Some(b'f') => self.literal("false", JsValue::Bool(false)),
461 Some(b'n') => self.literal("null", JsValue::Null),
462 Some(b'-' | b'0'..=b'9') => self.number(),
463 _ => Err(invalid("expected JSON value")),
464 }
465 }
466 fn literal(&mut self, text: &str, value: JsValue) -> Result<JsValue, serde_json::Error> {
467 if self
468 .input
469 .get(self.position..)
470 .is_some_and(|rest| rest.starts_with(text))
471 {
472 self.position += text.len();
473 Ok(value)
474 } else {
475 Err(invalid("invalid JSON literal"))
476 }
477 }
478 fn digits(&mut self) -> bool {
479 let start = self.position;
480 while self.peek().is_some_and(|b| b.is_ascii_digit()) {
481 self.position += 1;
482 }
483 self.position > start
484 }
485 fn number(&mut self) -> Result<JsValue, serde_json::Error> {
486 let start = self.position;
487 _ = self.take(b'-');
488 if !self.take(b'0') && !self.digits() {
489 return Err(invalid("invalid JSON number"));
490 }
491 if self.take(b'.') && !self.digits() {
492 return Err(invalid("invalid JSON fraction"));
493 }
494 if self.take(b'e') || self.take(b'E') {
495 _ = self.take(b'+') || self.take(b'-');
496 if !self.digits() {
497 return Err(invalid("invalid JSON exponent"));
498 }
499 }
500 self.input
501 .get(start..self.position)
502 .ok_or_else(|| invalid("invalid JSON number bounds"))?
503 .parse::<f64>()
504 .map(JsValue::Number)
505 .map_err(|_| invalid("invalid JSON number"))
506 }
507}
508
509pub fn parse_value(input: &str) -> Result<JsValue, serde_json::Error> {
516 let mut parser = Parser { input, position: 0 };
517 let value = parser.value(0)?;
518 parser.whitespace();
519 if parser.position != input.len() {
520 return Err(invalid("trailing JSON input"));
521 }
522 Ok(value)
523}
524
525pub fn from_slice<T: DeserializeOwned + 'static>(input: &[u8]) -> Result<T, serde_json::Error> {
529 let text = std::str::from_utf8(input).map_err(|_| invalid("invalid JSON UTF8"))?;
530 from_value(parse_value(text)?)
531}
532
533pub fn from_value<T: DeserializeOwned + 'static>(input: JsValue) -> Result<T, serde_json::Error> {
543 if TypeId::of::<T>() == TypeId::of::<Value>() {
544 let value: Box<dyn Any> = Box::new(input.to_json_value()?);
545 return value
546 .downcast::<T>()
547 .map(|value| *value)
548 .map_err(|_| invalid("invalid JSON value type"));
549 }
550 T::deserialize(input)
551}
552
553pub fn deserialize_value<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Value, D::Error> {
559 JsValue::deserialize(deserializer)?
560 .to_json_value()
561 .map_err(serde::de::Error::custom)
562}
563
564pub fn deserialize_optional_value<'de, D: Deserializer<'de>>(
570 deserializer: D,
571) -> Result<Option<Value>, D::Error> {
572 Option::<JsValue>::deserialize(deserializer)?
573 .map(|value| value.to_json_value().map_err(serde::de::Error::custom))
574 .transpose()
575}
576
577pub fn deserialize_map<'de, D: Deserializer<'de>>(
583 deserializer: D,
584) -> Result<serde_json::Map<String, Value>, D::Error> {
585 match deserialize_value(deserializer)? {
586 Value::Object(map) => Ok(map),
587 Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) | Value::Array(_) => {
588 Err(serde::de::Error::custom("expected JSON object"))
589 }
590 }
591}
592
593pub fn deserialize_optional_map<'de, D: Deserializer<'de>>(
599 deserializer: D,
600) -> Result<Option<serde_json::Map<String, Value>>, D::Error> {
601 deserialize_optional_value(deserializer)?
602 .map(|value| match value {
603 Value::Object(map) => Ok(map),
604 Value::Null
605 | Value::Bool(_)
606 | Value::Number(_)
607 | Value::String(_)
608 | Value::Array(_) => Err(serde::de::Error::custom("expected JSON object")),
609 })
610 .transpose()
611}
612
613pub fn deserialize_btree_map<'de, D: Deserializer<'de>>(
619 deserializer: D,
620) -> Result<std::collections::BTreeMap<String, Value>, D::Error> {
621 deserialize_map(deserializer).map(|map| map.into_iter().collect())
622}
623
624fn invalid(message: &str) -> serde_json::Error {
625 <serde_json::Error as serde::de::Error>::custom(message)
626}
627
628#[must_use]
629pub fn number_as_f64(number: &Number) -> Option<f64> {
630 number.as_f64()
631}
632
633pub fn number_to_string(number: &Number) -> Result<String, serde_json::Error> {
637 Ok(ryu_js::Buffer::new()
638 .format(number_as_f64(number).ok_or_else(|| invalid("invalid JSON number"))?)
639 .to_owned())
640}
641
642pub fn to_vec<T: Serialize + ?Sized>(data: &T) -> Result<Vec<u8>, serde_json::Error> {
648 let value = JsValue::from(serde_json::to_value(data)?);
649 let mut bytes = Vec::new();
650 write_value(&value, &mut bytes)?;
651 Ok(bytes)
652}
653
654pub fn to_string<T: Serialize + ?Sized>(data: &T) -> Result<String, serde_json::Error> {
658 String::from_utf8(to_vec(data)?).map_err(|_| invalid("invalid JSON output UTF8"))
659}
660
661pub fn to_value<T: Serialize + ?Sized>(data: &T) -> Result<Value, serde_json::Error> {
665 finite_value(&JsValue::from(serde_json::to_value(data)?))
666}
667
668pub fn serialize<T: Serialize + ?Sized, S: Serializer>(
672 data: &T,
673 serializer: S,
674) -> Result<S::Ok, S::Error> {
675 to_value(data)
676 .map_err(serde::ser::Error::custom)?
677 .serialize(serializer)
678}
679
680#[expect(
681 clippy::as_conversions,
682 clippy::cast_possible_truncation,
683 clippy::cast_sign_loss,
684 reason = "JavaScript-compatible numbers deliberately retain IEEE754 rounding and guarded integer coercion at the wire or adapter boundary"
685)]
686fn finite_value(value: &JsValue) -> Result<Value, serde_json::Error> {
687 match value {
688 JsValue::Null => Ok(Value::Null),
689 JsValue::Bool(b) => Ok(Value::Bool(*b)),
690 JsValue::Number(n) if !n.is_finite() => Ok(Value::Null),
691 JsValue::Number(n)
692 if n.fract() == 0.0
693 && (-9_223_372_036_854_776_000.0..9_223_372_036_854_776_000.0).contains(n) =>
694 {
695 Ok(Value::Number(Number::from(*n as i64)))
696 }
697 JsValue::Number(n)
698 if n.fract() == 0.0 && (0.0..18_446_744_073_709_552_000.0).contains(n) =>
699 {
700 Ok(Value::Number(Number::from(*n as u64)))
701 }
702 JsValue::Number(n) => Number::from_f64(*n)
703 .map(Value::Number)
704 .ok_or_else(|| invalid("invalid finite JSON number")),
705 JsValue::String(s) => Ok(Value::String(s.clone())),
706 JsValue::Date(value) => Ok(Value::String(
707 value.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
708 )),
709 JsValue::Array(a) => a
710 .iter()
711 .map(finite_value)
712 .collect::<Result<_, _>>()
713 .map(Value::Array),
714 JsValue::Object(m) => {
715 let mut entries: Vec<_> = m.iter().collect();
716 entries.sort_by_key(|(name, _)| array_index(name).unwrap_or(u32::MAX));
717 let mut values = serde_json::Map::new();
718 for (key, value) in entries {
719 _ = values.insert(key.clone(), finite_value(value)?);
720 }
721 Ok(Value::Object(values))
722 }
723 }
724}
725
726fn write_value(value: &JsValue, bytes: &mut Vec<u8>) -> Result<(), serde_json::Error> {
727 match value {
728 JsValue::Null => bytes.extend_from_slice(b"null"),
729 JsValue::Bool(true) => bytes.extend_from_slice(b"true"),
730 JsValue::Bool(false) => bytes.extend_from_slice(b"false"),
731 JsValue::Number(n) => {
732 if n.is_finite() {
733 bytes.extend_from_slice(ryu_js::Buffer::new().format_finite(*n).as_bytes());
734 } else {
735 bytes.extend_from_slice(b"null");
736 }
737 }
738 JsValue::String(s) => serde_json::to_writer(bytes, s)?,
739 JsValue::Date(value) => serde_json::to_writer(
740 bytes,
741 &value.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
742 )?,
743 JsValue::Array(a) => {
744 bytes.push(b'[');
745 for (index, item) in a.iter().enumerate() {
746 if index != 0 {
747 bytes.push(b',');
748 }
749 write_value(item, bytes)?;
750 }
751 bytes.push(b']');
752 }
753 JsValue::Object(m) => {
754 let mut entries: Vec<_> = m.iter().collect();
755 entries.sort_by_key(|(name, _)| array_index(name).unwrap_or(u32::MAX));
756 bytes.push(b'{');
757 for (index, (key, value)) in entries.into_iter().enumerate() {
758 if index != 0 {
759 bytes.push(b',');
760 }
761 serde_json::to_writer(&mut *bytes, key)?;
762 bytes.push(b':');
763 write_value(value, bytes)?;
764 }
765 bytes.push(b'}');
766 }
767 }
768 Ok(())
769}
770
771fn array_index(name: &str) -> Option<u32> {
772 let index = name.parse::<u32>().ok()?;
773 (index != u32::MAX && index.to_string() == name).then_some(index)
774}