use std::str::FromStr;
use super::core::Value;
impl From<Value> for serde_json::Value {
fn from(v: Value) -> Self {
match v {
Value::Null => serde_json::Value::Null,
Value::Bool(b) => serde_json::Value::Bool(b),
Value::Integer(i) => serde_json::json!(i),
Value::Float(f) => super::float_text::float_to_json(f),
Value::String(s) | Value::Uuid(s) | Value::Ulid(s) | Value::Regex(s) => {
serde_json::Value::String(s)
}
Value::Bytes(b) => serde_json::Value::String(hex::encode(b)),
Value::Array(arr) | Value::Set(arr) => {
serde_json::Value::Array(arr.into_iter().map(serde_json::Value::from).collect())
}
Value::Object(map) => serde_json::Value::Object(
map.into_iter()
.map(|(k, v)| (k, serde_json::Value::from(v)))
.collect(),
),
Value::DateTime(dt) | Value::NaiveDateTime(dt) => {
serde_json::Value::String(dt.to_string())
}
Value::Duration(d) => serde_json::Value::String(d.to_string()),
Value::Decimal(d) => {
let s = d.to_string();
match serde_json::Number::from_str(&s) {
Ok(n) if !(d.scale() == 0 && n.is_f64()) => serde_json::Value::Number(n),
_ => serde_json::Value::String(s),
}
}
Value::Geometry(g) => serde_json::to_value(g).unwrap_or(serde_json::Value::Null),
Value::Range { .. } | Value::Record { .. } => serde_json::Value::Null,
Value::Vector(v) => {
serde_json::Value::Array(
v.iter()
.map(|f| {
if f.is_finite() {
serde_json::json!(*f)
} else {
super::float_text::float_to_json(f64::from(*f))
}
})
.collect(),
)
}
Value::ArrayCell(cell) => {
let mut obj = serde_json::json!({
"coords": cell.coords.into_iter().map(serde_json::Value::from).collect::<Vec<_>>(),
"attrs": cell.attrs.into_iter().map(serde_json::Value::from).collect::<Vec<_>>(),
});
if let Some(ts) = cell.system_time {
obj["_ts_system"] = serde_json::json!(ts);
}
obj
}
}
}
}
impl From<serde_json::Value> for Value {
fn from(v: serde_json::Value) -> Self {
match v {
serde_json::Value::Null => Value::Null,
serde_json::Value::Bool(b) => Value::Bool(b),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
Value::Integer(i)
} else if let Some(u) = n.as_u64() {
Value::from_u64(u)
} else if let Some(f) = n.as_f64() {
Value::Float(f)
} else {
Value::Null
}
}
serde_json::Value::String(s) => Value::String(s),
serde_json::Value::Array(arr) => {
Value::Array(arr.into_iter().map(Value::from).collect())
}
serde_json::Value::Object(map) => {
Value::Object(map.into_iter().map(|(k, v)| (k, Value::from(v))).collect())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::array_cell::ArrayCell;
#[test]
fn json_u64_above_i64_max_keeps_its_number() {
let json: serde_json::Value =
sonic_rs::from_str("18446744073709551615").expect("parse u64::MAX");
assert_eq!(
Value::from(json),
Value::Decimal(rust_decimal::Decimal::from(u64::MAX))
);
let small: serde_json::Value = sonic_rs::from_str("42").expect("parse 42");
assert_eq!(Value::from(small), Value::Integer(42));
}
#[test]
fn integer_decimal_past_64_bits_keeps_exact_digits() {
let big = rust_decimal::Decimal::from_i128_with_scale(2 * i128::from(u64::MAX), 0);
let json = serde_json::Value::from(Value::Decimal(big));
assert_eq!(
json,
serde_json::Value::String("36893488147419103230".into())
);
let max = serde_json::Value::from(Value::Decimal(rust_decimal::Decimal::from(u64::MAX)));
assert_eq!(max, serde_json::json!(u64::MAX));
}
#[test]
fn decimal_to_json_is_number_not_string() {
let d = rust_decimal::Decimal::new(12345, 2); let json = serde_json::Value::from(Value::Decimal(d));
assert!(json.is_number(), "expected JSON Number, got {json:?}");
assert_eq!(json.to_string(), "123.45");
}
#[test]
fn json_lossy_uuid_becomes_string() {
let v = Value::Uuid("550e8400-e29b-41d4-a716-446655440000".into());
let json = serde_json::Value::from(v);
assert!(json.is_string(), "Uuid must serialize as JSON string");
let rt = Value::from(json);
assert!(
matches!(rt, Value::String(_)),
"Uuid round-trips through JSON as String, got {rt:?}"
);
}
#[test]
fn json_lossy_ulid_becomes_string() {
let v = Value::Ulid("01ARZ3NDEKTSV4RRFFQ69G5FAV".into());
let json = serde_json::Value::from(v);
assert!(json.is_string(), "Ulid must serialize as JSON string");
let rt = Value::from(json);
assert!(
matches!(rt, Value::String(_)),
"Ulid round-trips through JSON as String, got {rt:?}"
);
}
#[test]
fn json_lossy_regex_becomes_string() {
let v = Value::Regex(r"^\d+$".into());
let json = serde_json::Value::from(v);
assert!(json.is_string(), "Regex must serialize as JSON string");
let rt = Value::from(json);
assert!(
matches!(rt, Value::String(_)),
"Regex round-trips through JSON as String, got {rt:?}"
);
}
#[test]
fn json_lossy_range_becomes_null() {
let v = Value::Range {
start: Some(Box::new(Value::Integer(1))),
end: Some(Box::new(Value::Integer(10))),
inclusive: false,
};
let json = serde_json::Value::from(v);
assert!(
json.is_null(),
"Range must serialize as JSON null, got {json:?}"
);
let rt = Value::from(json);
assert!(
matches!(rt, Value::Null),
"Range round-trips through JSON as Null, got {rt:?}"
);
}
#[test]
fn json_lossy_record_becomes_null() {
let v = Value::Record {
table: "users".into(),
id: "abc123".into(),
};
let json = serde_json::Value::from(v);
assert!(
json.is_null(),
"Record must serialize as JSON null, got {json:?}"
);
let rt = Value::from(json);
assert!(
matches!(rt, Value::Null),
"Record round-trips through JSON as Null, got {rt:?}"
);
}
#[test]
fn json_lossy_array_cell_becomes_object_without_discriminator() {
let v = Value::ArrayCell(ArrayCell {
coords: vec![Value::Integer(1), Value::Integer(2)],
attrs: vec![Value::Float(3.5)],
system_time: None,
});
let json = serde_json::Value::from(v);
assert!(
json.is_object(),
"ArrayCell must serialize as JSON object, got {json:?}"
);
let obj = json.as_object().unwrap();
assert!(
obj.contains_key("coords"),
"ArrayCell JSON must have 'coords' key"
);
assert!(
obj.contains_key("attrs"),
"ArrayCell JSON must have 'attrs' key"
);
let rt = Value::from(json);
assert!(
matches!(rt, Value::Object(_)),
"ArrayCell round-trips through JSON as Object, got {rt:?}"
);
}
#[test]
fn non_finite_floats_render_postgres_text_not_null() {
assert_eq!(
serde_json::Value::from(Value::Float(f64::NAN)),
serde_json::json!("NaN")
);
assert_eq!(
serde_json::Value::from(Value::Array(vec![
Value::Float(f64::INFINITY),
Value::Float(f64::NEG_INFINITY),
Value::Float(1.5),
])),
serde_json::json!(["Infinity", "-Infinity", 1.5])
);
assert_eq!(
serde_json::Value::from(Value::Vector(vec![0.5, f32::NAN].into())),
serde_json::json!([0.5, "NaN"])
);
}
}