forma_json 0.1.0

JSON serialization and deserialization for forma_core.
Documentation
use forma_derive::{Deserialize, Serialize};
use forma_json::value::{from_value, to_value, Number};
use forma_json::{from_str, to_string, to_string_pretty, Value};
use std::collections::BTreeMap;

// ── Parsing into Value ──────────────────────────────────────────────

#[test]
fn test_parse_null() {
    let v: Value = from_str("null").unwrap();
    assert_eq!(v, Value::Null);
    assert!(v.is_null());
}

#[test]
fn test_parse_bool() {
    let v: Value = from_str("true").unwrap();
    assert_eq!(v, Value::Bool(true));
    assert_eq!(v.as_bool(), Some(true));

    let v: Value = from_str("false").unwrap();
    assert_eq!(v.as_bool(), Some(false));
}

#[test]
fn test_parse_number_positive() {
    let v: Value = from_str("42").unwrap();
    assert_eq!(v.as_u64(), Some(42));
    assert_eq!(v.as_i64(), Some(42));
}

#[test]
fn test_parse_number_negative() {
    let v: Value = from_str("-7").unwrap();
    assert_eq!(v.as_i64(), Some(-7));
}

#[test]
fn test_parse_number_float() {
    let v: Value = from_str("3.14").unwrap();
    assert!((v.as_f64().unwrap() - 3.14).abs() < f64::EPSILON);
}

#[test]
fn test_parse_string() {
    let v: Value = from_str("\"hello\"").unwrap();
    assert_eq!(v.as_str(), Some("hello"));
}

#[test]
fn test_parse_array() {
    let v: Value = from_str("[1, 2, 3]").unwrap();
    assert!(v.is_array());
    assert_eq!(v.as_array().unwrap().len(), 3);
    assert_eq!(v[0].as_u64(), Some(1));
    assert_eq!(v[1].as_u64(), Some(2));
    assert_eq!(v[2].as_u64(), Some(3));
}

#[test]
fn test_parse_object() {
    let v: Value = from_str("{\"name\":\"Alice\",\"age\":30}").unwrap();
    assert!(v.is_object());
    assert_eq!(v["name"].as_str(), Some("Alice"));
    assert_eq!(v["age"].as_u64(), Some(30));
}

#[test]
fn test_parse_nested() {
    let v: Value = from_str(r#"{"users":[{"name":"Alice"},{"name":"Bob"}]}"#).unwrap();
    assert_eq!(v["users"][0]["name"].as_str(), Some("Alice"));
    assert_eq!(v["users"][1]["name"].as_str(), Some("Bob"));
}

// ── Serializing Value ───────────────────────────────────────────────

#[test]
fn test_serialize_null() {
    assert_eq!(to_string(&Value::Null).unwrap(), "null");
}

#[test]
fn test_serialize_bool() {
    assert_eq!(to_string(&Value::Bool(true)).unwrap(), "true");
}

#[test]
fn test_serialize_number() {
    assert_eq!(
        to_string(&Value::Number(Number::PosInt(42))).unwrap(),
        "42"
    );
    assert_eq!(
        to_string(&Value::Number(Number::NegInt(-7))).unwrap(),
        "-7"
    );
    assert_eq!(
        to_string(&Value::Number(Number::Float(3.14))).unwrap(),
        "3.14"
    );
}

#[test]
fn test_serialize_string() {
    assert_eq!(
        to_string(&Value::String("hello".into())).unwrap(),
        "\"hello\""
    );
}

#[test]
fn test_serialize_array() {
    let v = Value::Array(vec![Value::from(1i64), Value::from(2i64), Value::from(3i64)]);
    assert_eq!(to_string(&v).unwrap(), "[1,2,3]");
}

#[test]
fn test_serialize_object() {
    let mut m = BTreeMap::new();
    m.insert("a".to_string(), Value::from(1i64));
    m.insert("b".to_string(), Value::from(2i64));
    assert_eq!(to_string(&Value::Object(m)).unwrap(), "{\"a\":1,\"b\":2}");
}

// ── Roundtrip ───────────────────────────────────────────────────────

#[test]
fn test_value_roundtrip() {
    let input = r#"{"array":[1,2,3],"bool":true,"null":null,"number":42,"string":"hello"}"#;
    let v: Value = from_str(input).unwrap();
    let output = to_string(&v).unwrap();
    assert_eq!(output, input);
}

#[test]
fn test_value_pretty() {
    let mut m = BTreeMap::new();
    m.insert("x".to_string(), Value::from(1i64));
    m.insert("y".to_string(), Value::from(2i64));
    let v = Value::Object(m);
    let pretty = to_string_pretty(&v).unwrap();
    assert_eq!(pretty, "{\n  \"x\": 1,\n  \"y\": 2\n}");
}

// ── From impls ──────────────────────────────────────────────────────

#[test]
fn test_from_bool() {
    assert_eq!(Value::from(true), Value::Bool(true));
}

#[test]
fn test_from_i64() {
    assert_eq!(Value::from(42i64), Value::Number(Number::PosInt(42)));
    assert_eq!(Value::from(-1i64), Value::Number(Number::NegInt(-1)));
}

#[test]
fn test_from_u64() {
    assert_eq!(Value::from(42u64), Value::Number(Number::PosInt(42)));
}

#[test]
fn test_from_f64() {
    assert_eq!(Value::from(3.14f64), Value::Number(Number::Float(3.14)));
}

#[test]
fn test_from_string() {
    assert_eq!(Value::from("hello"), Value::String("hello".into()));
}

#[test]
fn test_from_vec() {
    let v: Value = vec![1i64, 2, 3].into();
    assert!(v.is_array());
    assert_eq!(v[0].as_i64(), Some(1));
}

#[test]
fn test_from_option() {
    assert_eq!(Value::from(Some(42i64)), Value::Number(Number::PosInt(42)));
    assert_eq!(Value::from(None::<i64>), Value::Null);
}

// ── Index ───────────────────────────────────────────────────────────

#[test]
fn test_index_missing_key() {
    let v: Value = from_str("{}").unwrap();
    assert!(v["missing"].is_null());
}

#[test]
fn test_index_out_of_bounds() {
    let v: Value = from_str("[1]").unwrap();
    assert!(v[99].is_null());
}

#[test]
fn test_index_wrong_type() {
    let v = Value::from(42i64);
    assert!(v["key"].is_null());
    assert!(v[0].is_null());
}

// ── from_value / to_value ───────────────────────────────────────────

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Point {
    x: i32,
    y: i32,
}

#[test]
fn test_to_value() {
    let p = Point { x: 1, y: 2 };
    let v = to_value(&p).unwrap();
    assert_eq!(v["x"].as_i64(), Some(1));
    assert_eq!(v["y"].as_i64(), Some(2));
}

#[test]
fn test_from_value() {
    let mut m = BTreeMap::new();
    m.insert("x".to_string(), Value::from(10i64));
    m.insert("y".to_string(), Value::from(20i64));
    let v = Value::Object(m);
    let p: Point = from_value(v).unwrap();
    assert_eq!(p, Point { x: 10, y: 20 });
}

// ── Display ─────────────────────────────────────────────────────────

#[test]
fn test_display() {
    let v = Value::from("hello");
    assert_eq!(format!("{v}"), "\"hello\"");
}