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