Skip to main content

taino_edit_core/
json.rs

1//! JSON (de)serialization of documents.
2//!
3//! The wire format mirrors ProseMirror's: an object with `type`, optional
4//! `attrs`, optional `content` (array), optional `marks` (array), and `text`
5//! for text nodes. Deserialization is **schema-checked** — content that
6//! violates the parent's content expression is rejected — so a document
7//! round-trips through JSON without loss.
8
9use serde_json::{Map, Value};
10
11use crate::attrs::Attrs;
12use crate::error::DocError;
13use crate::mark::Mark;
14use crate::node::Node;
15use crate::schema::Schema;
16
17impl Mark {
18    /// Serialize this mark to JSON.
19    pub fn to_json(&self) -> Value {
20        let mut obj = Map::new();
21        obj.insert("type".into(), Value::String(self.mark_type().name().into()));
22        if !self.attrs().is_empty() {
23            obj.insert(
24                "attrs".into(),
25                Value::Object(self.attrs().clone().into_iter().collect()),
26            );
27        }
28        Value::Object(obj)
29    }
30}
31
32impl Node {
33    /// Serialize this node (and its subtree) to JSON.
34    pub fn to_json(&self) -> Value {
35        let mut obj = Map::new();
36        obj.insert("type".into(), Value::String(self.node_type().name().into()));
37        if let Some(text) = self.text() {
38            obj.insert("text".into(), Value::String(text.into()));
39        }
40        if !self.attrs().is_empty() {
41            obj.insert(
42                "attrs".into(),
43                Value::Object(self.attrs().clone().into_iter().collect()),
44            );
45        }
46        if self.child_count() > 0 {
47            let content: Vec<Value> = self.content().iter().map(Node::to_json).collect();
48            obj.insert("content".into(), Value::Array(content));
49        }
50        if !self.marks().is_empty() {
51            let marks: Vec<Value> = self.marks().iter().map(Mark::to_json).collect();
52            obj.insert("marks".into(), Value::Array(marks));
53        }
54        Value::Object(obj)
55    }
56}
57
58fn obj<'a>(v: &'a Value, ctx: &str) -> Result<&'a Map<String, Value>, DocError> {
59    v.as_object()
60        .ok_or_else(|| DocError::MalformedJson(format!("{ctx}: expected an object")))
61}
62
63fn attrs_from(v: Option<&Value>) -> Result<Attrs, DocError> {
64    match v {
65        None => Ok(Attrs::new()),
66        Some(Value::Object(m)) => Ok(m.clone().into_iter().collect()),
67        Some(_) => Err(DocError::MalformedJson("`attrs` must be an object".into())),
68    }
69}
70
71impl Schema {
72    /// Parse a mark from JSON, validating its type against the schema.
73    pub fn mark_from_json(&self, v: &Value) -> Result<Mark, DocError> {
74        let m = obj(v, "mark")?;
75        let name = m
76            .get("type")
77            .and_then(Value::as_str)
78            .ok_or_else(|| DocError::MalformedJson("mark: missing `type`".into()))?;
79        let mt = self
80            .mark_type(name)
81            .ok_or_else(|| DocError::UnknownMarkType(name.to_string()))?
82            .clone();
83        Ok(mt.create(attrs_from(m.get("attrs"))?))
84    }
85
86    fn marks_from(&self, v: Option<&Value>) -> Result<Vec<Mark>, DocError> {
87        match v {
88            None => Ok(Vec::new()),
89            Some(Value::Array(a)) => a.iter().map(|m| self.mark_from_json(m)).collect(),
90            Some(_) => Err(DocError::MalformedJson("`marks` must be an array".into())),
91        }
92    }
93
94    /// Parse a node (and its subtree) from JSON, validating every node
95    /// against the schema (unknown types and invalid content are rejected).
96    pub fn node_from_json(&self, v: &Value) -> Result<Node, DocError> {
97        let m = obj(v, "node")?;
98        let name = m
99            .get("type")
100            .and_then(Value::as_str)
101            .ok_or_else(|| DocError::MalformedJson("node: missing `type`".into()))?;
102        let marks = self.marks_from(m.get("marks"))?;
103
104        if let Some(text) = m.get("text") {
105            let text = text
106                .as_str()
107                .ok_or_else(|| DocError::MalformedJson("`text` must be a string".into()))?;
108            return self.text(text, marks);
109        }
110
111        let children = match m.get("content") {
112            None => Vec::new(),
113            Some(Value::Array(a)) => a
114                .iter()
115                .map(|c| self.node_from_json(c))
116                .collect::<Result<Vec<_>, _>>()?,
117            Some(_) => return Err(DocError::MalformedJson("`content` must be an array".into())),
118        };
119        self.node(name, attrs_from(m.get("attrs"))?, children, marks)
120    }
121
122    /// Parse a document from a JSON string.
123    pub fn parse_json_str(&self, s: &str) -> Result<Node, DocError> {
124        let v: Value =
125            serde_json::from_str(s).map_err(|e| DocError::MalformedJson(e.to_string()))?;
126        self.node_from_json(&v)
127    }
128}