Skip to main content

concinnity_world/spec/
json.rs

1// The JSON bridge for typed specs.
2//
3// Turns `AssetSpec` / `ArgValue` data into the engine's `serde_json::Value`, so
4// a consumer never parses a JSON string: the cook pipeline builds UI assets from
5// the shared `asset::` builders, and authoring consumers turn a spec into a
6// world-line value.
7
8use super::{ArgValue, AssetSpec};
9use serde_json::{Map, Number, Value};
10
11/// One `ArgValue` as a `serde_json::Value`.
12pub fn arg_value_to_json(v: &ArgValue) -> Value {
13    match v {
14        ArgValue::Null => Value::Null,
15        ArgValue::Bool(b) => Value::Bool(*b),
16        ArgValue::Int(i) => Value::Number(Number::from(*i)),
17        ArgValue::Float(f) => Number::from_f64(*f)
18            .map(Value::Number)
19            .unwrap_or(Value::Null),
20        ArgValue::Str(s) => Value::String(s.clone()),
21        ArgValue::Array(items) => Value::Array(items.iter().map(arg_value_to_json).collect()),
22        ArgValue::Object(fields) => Value::Object(object_map(fields)),
23    }
24}
25
26/// A spec's `args` object as a `serde_json::Value` (what a component deserializes
27/// from).
28pub fn spec_args(spec: &AssetSpec) -> Value {
29    Value::Object(object_map(&spec.fields))
30}
31
32/// A spec as a full world-line value: `{"name", "type", "args"}`, the shape
33/// `parse_world_jsonl` yields and the cook pipeline validates.
34pub fn spec_to_value(spec: &AssetSpec) -> Value {
35    let mut obj = Map::new();
36    obj.insert("name".to_string(), Value::String(spec.name.clone()));
37    obj.insert(
38        "type".to_string(),
39        Value::String(spec.asset_type.to_string()),
40    );
41    obj.insert("args".to_string(), spec_args(spec));
42    Value::Object(obj)
43}
44
45fn object_map(fields: &[(String, ArgValue)]) -> Map<String, Value> {
46    let mut obj = Map::new();
47    for (k, v) in fields {
48        obj.insert(k.clone(), arg_value_to_json(v));
49    }
50    obj
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn arg_value_maps_each_shape() {
59        assert_eq!(arg_value_to_json(&ArgValue::Bool(true)), Value::Bool(true));
60        assert_eq!(arg_value_to_json(&ArgValue::Int(48)), serde_json::json!(48));
61        assert_eq!(
62            arg_value_to_json(&ArgValue::floats(&[1.0, 0.0, 0.0])),
63            serde_json::json!([1.0, 0.0, 0.0])
64        );
65    }
66
67    #[test]
68    fn spec_args_wraps_fields_in_order() {
69        let spec = AssetSpec::new("s", "Sprite")
70            .set("x", 10.0f32)
71            .set("y", 20.0f32);
72        let v = spec_args(&spec);
73        assert_eq!(v["x"], 10.0);
74        assert_eq!(v["y"], 20.0);
75    }
76
77    #[test]
78    fn spec_to_value_wraps_name_type_args() {
79        let spec = AssetSpec::new("lamp", "PointLight").set("intensity", 8.0f32);
80        let v = spec_to_value(&spec);
81        assert_eq!(v["name"], "lamp");
82        assert_eq!(v["type"], "PointLight");
83        assert_eq!(v["args"]["intensity"], 8.0);
84    }
85}