use std::collections::BTreeMap;
use serde::Deserialize;
use jiter::serde::{Error, JiterDeserializer, from_slice, from_str};
fn de_f64_inf_nan(s: &str) -> f64 {
let mut de = JiterDeserializer::new(s.as_bytes()).with_allow_inf_nan();
let v = f64::deserialize(&mut de).unwrap();
de.finish().unwrap();
v
}
fn de_value_inf_nan(s: &str) -> serde_json::Value {
let mut de = JiterDeserializer::new(s.as_bytes()).with_allow_inf_nan();
let v = serde_json::Value::deserialize(&mut de).unwrap();
de.finish().unwrap();
v
}
#[test]
fn primitives() {
assert!(from_str::<bool>("true").unwrap());
assert_eq!(from_str::<i64>("-42").unwrap(), -42);
assert_eq!(from_str::<u64>("42").unwrap(), 42);
assert!((from_str::<f64>("2.5").unwrap() - 2.5).abs() < f64::EPSILON);
assert_eq!(from_str::<String>(r#""hello""#).unwrap(), "hello");
assert_eq!(from_str::<Option<i64>>("null").unwrap(), None);
assert_eq!(from_str::<Option<i64>>("5").unwrap(), Some(5));
assert_eq!(from_str::<()>("null").unwrap(), ());
}
#[test]
fn vecs_and_maps() {
assert_eq!(from_str::<Vec<i64>>("[1, 2, 3]").unwrap(), vec![1, 2, 3]);
assert_eq!(from_str::<Vec<i64>>("[]").unwrap(), Vec::<i64>::new());
let map: BTreeMap<String, i64> = from_str(r#"{"a": 1, "b": 2}"#).unwrap();
assert_eq!(map, BTreeMap::from([("a".to_string(), 1), ("b".to_string(), 2)]));
let map: BTreeMap<u32, String> = from_str(r#"{"1": "one", "2": "two"}"#).unwrap();
assert_eq!(map, BTreeMap::from([(1, "one".to_string()), (2, "two".to_string())]));
}
#[derive(Deserialize, PartialEq, Debug)]
struct Person<'a> {
name: &'a str,
age: u8,
phones: Vec<&'a str>,
}
#[test]
fn borrowed_struct() {
let data = r#"{"name": "John Doe", "age": 43, "phones": ["+44 111", "+44 222"]}"#;
let person: Person = from_str(data).unwrap();
assert_eq!(
person,
Person {
name: "John Doe",
age: 43,
phones: vec!["+44 111", "+44 222"],
}
);
}
#[test]
fn zero_copy_borrowed_str() {
let data = br#""no escapes here""#;
let s: &str = from_slice(data).unwrap();
assert_eq!(s, "no escapes here");
let input = data.as_ptr() as usize..(data.as_ptr() as usize + data.len());
assert!(
input.contains(&(s.as_ptr() as usize)),
"string should borrow from input"
);
}
#[test]
fn escaped_string_is_owned() {
let s: String = from_str(r#""tab\tnewline\n""#).unwrap();
assert_eq!(s, "tab\tnewline\n");
}
#[test]
fn borrowed_str_with_escape_errors() {
let err = from_str::<&str>(r#""has \t escape""#).unwrap_err();
assert!(matches!(err, Error::Data { .. }));
}
#[derive(Deserialize, PartialEq, Debug)]
struct Nested {
inner: Inner,
list: Vec<Inner>,
}
#[derive(Deserialize, PartialEq, Debug)]
struct Inner {
value: i64,
}
#[test]
fn nested() {
let data = r#"{"inner": {"value": 1}, "list": [{"value": 2}, {"value": 3}]}"#;
let nested: Nested = from_str(data).unwrap();
assert_eq!(
nested,
Nested {
inner: Inner { value: 1 },
list: vec![Inner { value: 2 }, Inner { value: 3 }],
}
);
}
#[derive(Deserialize, PartialEq, Debug)]
enum Shape {
Unit,
Newtype(i64),
Tuple(i64, i64),
Struct { x: i64, y: i64 },
}
#[test]
fn enums() {
assert_eq!(from_str::<Shape>(r#""Unit""#).unwrap(), Shape::Unit);
assert_eq!(from_str::<Shape>(r#"{"Newtype": 7}"#).unwrap(), Shape::Newtype(7));
assert_eq!(from_str::<Shape>(r#"{"Tuple": [1, 2]}"#).unwrap(), Shape::Tuple(1, 2));
assert_eq!(
from_str::<Shape>(r#"{"Struct": {"x": 1, "y": 2}}"#).unwrap(),
Shape::Struct { x: 1, y: 2 }
);
}
#[derive(Deserialize, PartialEq, Debug)]
struct Ordered {
a: i64,
b: String,
c: bool,
#[serde(default)]
d: Option<i64>,
}
#[test]
fn field_order_independent() {
let declared = Ordered {
a: 1,
b: "two".to_string(),
c: true,
d: Some(4),
};
assert_eq!(
from_str::<Ordered>(r#"{"a": 1, "b": "two", "c": true, "d": 4}"#).unwrap(),
declared
);
assert_eq!(
from_str::<Ordered>(r#"{"d": 4, "c": true, "b": "two", "a": 1}"#).unwrap(),
declared
);
let shuffled = r#"{"c": true, "x": [1, 2, 3], "b": "two", "y": {"z": null}, "a": 1}"#;
assert_eq!(
from_str::<Ordered>(shuffled).unwrap(),
Ordered {
a: 1,
b: "two".to_string(),
c: true,
d: None,
}
);
}
#[test]
fn skips_unknown_fields() {
let data = r#"{"value": 5, "unknown": [1, 2, {"deep": "nested"}], "more": null}"#;
let inner: Inner = from_str(data).unwrap();
assert_eq!(inner, Inner { value: 5 });
}
#[test]
fn trailing_data_errors() {
assert!(from_str::<i64>("1 2").is_err());
assert!(from_str::<Vec<i64>>("[1, 2] extra").is_err());
}
#[test]
fn wrong_type_errors() {
let err = from_str::<i64>(r#""not a number""#).unwrap_err();
assert!(matches!(err, Error::Data { .. }));
}
#[test]
fn type_error_carries_real_position() {
#[derive(Deserialize, Debug)]
#[allow(dead_code)]
struct S {
a: i64,
b: i64,
}
let data = r#"{"a": 1, "b": "oops"}"#;
let err = from_str::<S>(data).unwrap_err();
assert!(matches!(err, Error::Data { .. }));
assert_eq!(err.index(), Some(data.find("\"oops\"").unwrap()));
}
#[test]
fn top_level_unknown_variant_is_positioned() {
let err = from_str::<Shape>(r#"{"Nope": 1}"#).unwrap_err();
assert!(matches!(err, Error::Data { .. }), "got {err:?}");
assert!(err.index().is_some(), "unknown-variant error should carry a position");
}
#[test]
fn error_resolves_to_line_and_column() {
#[derive(Deserialize, Debug)]
#[allow(dead_code)]
struct S {
a: i64,
b: i64,
}
let data = "{\n \"a\": 1,\n \"b\": \"oops\"\n}";
let err = from_str::<S>(data).unwrap_err();
let pos = err.get_position(data.as_bytes()).unwrap();
assert_eq!(pos.line, 3);
assert!(err.description(data.as_bytes()).contains("line 3 column"));
}
#[test]
fn from_slice_works() {
let v: Vec<u8> = from_slice(b"[1, 2, 3]").unwrap();
assert_eq!(v, vec![1, 2, 3]);
}
#[test]
fn recursion_limit_default_rejects_deep_nesting() {
let deep = format!("{}{}", "[".repeat(300), "]".repeat(300));
let err = from_str::<serde_json::Value>(&deep).unwrap_err();
assert!(matches!(&err, Error::Syntax(e) if e.error_type == jiter::JsonErrorType::RecursionLimitExceeded));
}
#[test]
fn recursion_limit_configurable() {
let json = "[[[[[1]]]]]"; let mut de = JiterDeserializer::new(json.as_bytes()).with_recursion_limit(3);
assert!(serde_json::Value::deserialize(&mut de).is_err());
let mut de = JiterDeserializer::new(json.as_bytes()).with_recursion_limit(10);
assert!(serde_json::Value::deserialize(&mut de).is_ok());
}
#[test]
fn recursion_limit_disabled_allows_deep_nesting() {
let deep = format!("{}1{}", "[".repeat(300), "]".repeat(300));
let mut de = JiterDeserializer::new(deep.as_bytes()).disable_recursion_limit();
assert!(serde_json::Value::deserialize(&mut de).is_ok());
de.finish().unwrap();
}
#[test]
fn extra_tuple_elements_rejected() {
assert_eq!(from_str::<(i64, i64)>("[1, 2]").unwrap(), (1, 2));
assert!(from_str::<(i64, i64)>("[1, 2, 3]").is_err());
assert!(from_str::<[i64; 2]>("[1, 2, 3]").is_err());
}
#[derive(Deserialize, Debug, PartialEq)]
enum Tree {
Leaf,
Node(Box<Tree>),
}
#[test]
fn recursion_limit_counts_enum_wrappers() {
let nested = r#"{"Node":{"Node":{"Node":"Leaf"}}}"#; let mut de = JiterDeserializer::new(nested.as_bytes()).with_recursion_limit(2);
assert!(Tree::deserialize(&mut de).is_err());
let mut de = JiterDeserializer::new(nested.as_bytes()).with_recursion_limit(10);
assert!(Tree::deserialize(&mut de).is_ok());
}
#[test]
fn unit_variant_object_form() {
assert_eq!(from_str::<Shape>(r#"{"Unit": null}"#).unwrap(), Shape::Unit);
assert_eq!(from_str::<Shape>(r#""Unit""#).unwrap(), Shape::Unit);
assert!(from_str::<Shape>(r#"{"Unit": 5}"#).is_err());
}
#[test]
fn recursion_limit_applies_to_skipped_fields() {
#[derive(Deserialize, Debug)]
#[allow(dead_code)]
struct S {
keep: i64,
}
let deep = format!(r#"{{"skip": {}1{}, "keep": 5}}"#, "[".repeat(230), "]".repeat(230));
assert!(from_str::<S>(&deep).is_err());
let mut de = JiterDeserializer::new(deep.as_bytes()).disable_recursion_limit();
assert_eq!(S::deserialize(&mut de).unwrap().keep, 5);
}
#[test]
fn big_integers() {
let n: i128 = from_str("170141183460469231731687303715884105727").unwrap();
assert_eq!(n, i128::MAX);
let n: u128 = from_str("340282366920938463463374607431768211455").unwrap();
assert_eq!(n, u128::MAX);
assert!(from_str::<i64>("99999999999999999999999999999999").is_err());
let huge = format!("1{}", "0".repeat(40)); let f: f64 = from_str(&huge).unwrap();
assert!((f - 1e40).abs() / 1e40 < 1e-10);
}
#[test]
fn big_floats() {
let big: f64 = from_str("1e308").unwrap();
assert!(big > 1e307 && big.is_finite());
let small: f64 = from_str("1e-308").unwrap();
assert!(small > 0.0 && small < 1e-307);
let pi: f64 = from_str("3.141592653589793238462643383279").unwrap();
assert!((pi - std::f64::consts::PI).abs() < 1e-15);
}
#[test]
fn nan_and_infinity_rejected_by_default() {
assert!(from_str::<f64>("NaN").is_err());
assert!(from_str::<f64>("Infinity").is_err());
assert!(from_str::<f64>("-Infinity").is_err());
}
#[test]
fn nan_and_infinity_allowed_when_enabled() {
assert!(de_f64_inf_nan("NaN").is_nan());
let inf = de_f64_inf_nan("Infinity");
assert!(inf.is_infinite() && inf.is_sign_positive());
let ninf = de_f64_inf_nan("-Infinity");
assert!(ninf.is_infinite() && ninf.is_sign_negative());
}
#[test]
fn non_finite_into_value_becomes_string() {
use serde_json::Value;
assert_eq!(de_value_inf_nan("Infinity"), Value::String("Infinity".into()));
assert_eq!(de_value_inf_nan("-Infinity"), Value::String("-Infinity".into()));
assert_eq!(de_value_inf_nan("NaN"), Value::String("NaN".into()));
assert_eq!(de_value_inf_nan("2.5"), Value::from(2.5));
}
#[test]
fn non_finite_into_f64_keeps_real_value() {
#[derive(Deserialize)]
struct S {
x: f64,
y: f64,
}
let mut de = JiterDeserializer::new(br#"{"x": Infinity, "y": NaN}"#).with_allow_inf_nan();
let s = S::deserialize(&mut de).unwrap();
de.finish().unwrap();
assert!(s.x.is_infinite() && s.x.is_sign_positive());
assert!(s.y.is_nan());
}
#[test]
fn overflow_into_value_no_longer_null() {
let v: serde_json::Value = from_str("1e400").unwrap();
assert_eq!(v, serde_json::Value::String("Infinity".into()));
let f: f64 = from_str("1e400").unwrap();
assert!(f.is_infinite());
}
#[test]
fn matches_serde_json() {
#[derive(Deserialize, PartialEq, Debug)]
struct Doc {
id: u64,
title: String,
tags: Vec<String>,
active: bool,
score: f64,
meta: Option<BTreeMap<String, String>>,
}
let data = r#"
{
"id": 12345,
"title": "A \"quoted\" title",
"tags": ["a", "b", "c"],
"active": true,
"score": 9.5,
"meta": {"k": "v"}
}"#;
let from_jiter: Doc = from_str(data).unwrap();
let from_serde: Doc = serde_json::from_str(data).unwrap();
assert_eq!(from_jiter, from_serde);
}
use proptest::prelude::*;
use serde_json::Value;
fn finite_f64() -> impl Strategy<Value = f64> {
prop::num::f64::NORMAL | prop::num::f64::SUBNORMAL | prop::num::f64::ZERO
}
#[cfg(feature = "num-bigint")]
fn json_int() -> impl Strategy<Value = i64> {
any::<i64>()
}
#[cfg(not(feature = "num-bigint"))]
fn json_int() -> impl Strategy<Value = i64> {
-999_999_999_999_999_999_i64..=999_999_999_999_999_999_i64
}
fn json_value() -> impl Strategy<Value = Value> {
let leaf = prop_oneof![
Just(Value::Null),
any::<bool>().prop_map(Value::Bool),
json_int().prop_map(|i| Value::Number(i.into())),
finite_f64().prop_map(|f| Value::Number(serde_json::Number::from_f64(f).unwrap())),
any::<String>().prop_map(Value::String),
];
leaf.prop_recursive(5, 64, 10, |inner| {
prop_oneof![
prop::collection::vec(inner.clone(), 0..10).prop_map(Value::Array),
prop::collection::hash_map(any::<String>(), inner, 0..10)
.prop_map(|m| Value::Object(m.into_iter().collect())),
]
})
}
fn number_eq(a: &serde_json::Number, b: &serde_json::Number) -> bool {
if let (Some(x), Some(y)) = (a.as_i64(), b.as_i64()) {
return x == y;
}
if let (Some(x), Some(y)) = (a.as_u64(), b.as_u64()) {
return x == y;
}
matches!((a.as_f64(), b.as_f64()), (Some(x), Some(y)) if x == y)
}
fn json_eq(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Null, Value::Null) => true,
(Value::Bool(x), Value::Bool(y)) => x == y,
(Value::String(x), Value::String(y)) => x == y,
(Value::Number(x), Value::Number(y)) => number_eq(x, y),
(Value::Array(x), Value::Array(y)) => x.len() == y.len() && x.iter().zip(y).all(|(a, b)| json_eq(a, b)),
(Value::Object(x), Value::Object(y)) => {
x.len() == y.len() && x.iter().all(|(k, v)| y.get(k).is_some_and(|w| json_eq(v, w)))
}
_ => false,
}
}
proptest! {
#[test]
fn proptest_matches_serde_json(value in json_value()) {
let json = serde_json::to_string(&value).unwrap();
let jiter: Value = from_str(&json).unwrap();
let serde: Value = serde_json::from_str(&json).unwrap();
prop_assert!(
json_eq(&jiter, &serde),
"mismatch for {json}\n jiter: {jiter:?}\n serde: {serde:?}"
);
}
}