use crate::{KnowledgeGraph, Result};
use serde_json::{json, Value};
use std::io::Write;
pub struct JsonLd;
impl JsonLd {
pub fn write<W: Write>(kg: &KnowledgeGraph, mut writer: W) -> Result<()> {
let doc = Self::to_value(kg);
let json = serde_json::to_string_pretty(&doc)?;
writer.write_all(json.as_bytes())?;
Ok(())
}
pub fn to_value(kg: &KnowledgeGraph) -> Value {
let mut graph = Vec::new();
let mut by_subject: std::collections::HashMap<String, Vec<_>> =
std::collections::HashMap::new();
for triple in kg.triples() {
by_subject
.entry(triple.subject().as_str().to_string())
.or_default()
.push(triple);
}
for (subject, triples) in by_subject {
let mut node = json!({
"@id": subject,
});
for triple in triples {
let pred = triple.predicate().as_str();
let obj = triple.object().as_str();
let obj_value = if obj.starts_with("http://") || obj.starts_with("https://") {
json!({ "@id": obj })
} else {
json!(obj)
};
if let Some(existing) = node.get_mut(pred) {
if let Some(arr) = existing.as_array_mut() {
arr.push(obj_value);
} else {
let old = existing.take();
*existing = json!([old, obj_value]);
}
} else {
node[pred] = obj_value;
}
}
graph.push(node);
}
json!({
"@context": {
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"xsd": "http://www.w3.org/2001/XMLSchema#",
"prov": "http://www.w3.org/ns/prov#"
},
"@graph": graph
})
}
pub fn to_string(kg: &KnowledgeGraph) -> Result<String> {
let mut buf = Vec::new();
Self::write(kg, &mut buf)?;
Ok(String::from_utf8_lossy(&buf).to_string())
}
pub fn from_value(doc: &Value) -> Result<KnowledgeGraph> {
let mut kg = KnowledgeGraph::new();
if let Some(graph) = doc.get("@graph").and_then(|v| v.as_array()) {
for node in graph {
Self::parse_node(node, &mut kg)?;
}
} else if doc.get("@id").is_some() {
Self::parse_node(doc, &mut kg)?;
}
Ok(kg)
}
fn parse_node(node: &Value, kg: &mut KnowledgeGraph) -> Result<()> {
let subject = node
.get("@id")
.and_then(|v| v.as_str())
.ok_or_else(|| crate::Error::ParseTriple("Missing @id".into()))?;
for (key, value) in node.as_object().into_iter().flatten() {
if key.starts_with('@') {
continue;
}
let values: Vec<&Value> = if let Some(arr) = value.as_array() {
arr.iter().collect()
} else {
vec![value]
};
for val in values {
let object = if let Some(id) = val.get("@id").and_then(|v| v.as_str()) {
id.to_string()
} else if let Some(s) = val.as_str() {
s.to_string()
} else {
continue;
};
kg.add_triple(crate::Triple::new(subject, key.as_str(), object));
}
}
Ok(())
}
pub fn parse(s: &str) -> Result<KnowledgeGraph> {
let doc: Value = serde_json::from_str(s)?;
Self::from_value(&doc)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Triple;
#[test]
fn test_roundtrip() {
let mut kg = KnowledgeGraph::new();
kg.add_triple(Triple::new(
"http://example.org/Apple",
"http://example.org/founded_by",
"http://example.org/Steve_Jobs",
));
let json = JsonLd::to_string(&kg).unwrap();
let parsed = JsonLd::parse(&json).unwrap();
assert_eq!(parsed.triple_count(), 1);
}
}