Skip to content

Commit e635960

Browse files
authored
Merge pull request #1 from DeciSym/bharathselvaraj/json2rdf-fixes
bharathselvaraj/json2rdf fixes
2 parents 714e59d + 5d29cf1 commit e635960

8 files changed

Lines changed: 540 additions & 259 deletions

File tree

Cargo.lock

Lines changed: 175 additions & 126 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
[package]
22
name = "json2rdf"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
edition = "2021"
55

66
[dependencies]
7+
clap = { version = "4.5.20", features = ["derive"] }
8+
log = "0.4.22"
79
oxrdf = "0.1.7"
8-
petgraph = "0.6.5"
10+
911

1012
serde = "1.0.203"
1113
serde_json = "1.0.117"
12-
struson = "0.5.0"
14+
uuid = { version = "1", features = ["v4"] }

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
# json2rdf
2-
CLI tool for converting JSON to RDF
2+
3+
This Rust-based tool converts JSON data into RDF format, utilizing the `oxrdf` crate for RDF graph handling and `serde_json` for efficient JSON parsing. It supports lightweight, memory-efficient processing by reading JSON data values sequentially, which makes it suitable for large datasets.
4+

src/airplane.json

Lines changed: 0 additions & 11 deletions
This file was deleted.

src/lib.rs

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
//! # JSON2RDF Converter Library
2+
//!
3+
//! This library provides functionality for converting JSON data into RDF format.
4+
//! It uses `serde_json` for JSON parsing and `oxrdf` to build and manage RDF graphs.
5+
//!
6+
//! ## Overview
7+
//! - Converts JSON data structures into RDF triples, generating a graph representation.
8+
//! - Supports blank nodes for nested structures and maps JSON properties to RDF predicates.
9+
//!
10+
//! ## Features
11+
//! - Handles JSON Objects, Arrays, Booleans, Numbers, and Strings as RDF triples.
12+
//! - Allows specifying a custom RDF namespace for generated predicates and objects.
13+
//! - Outputs the RDF data to a specified file or prints it to the console.
14+
15+
use oxrdf::vocab::xsd;
16+
use oxrdf::{BlankNode, Graph, Literal, NamedNodeRef, TripleRef};
17+
18+
use serde_json::{Deserializer, Value};
19+
use std::collections::VecDeque;
20+
use std::fs::File;
21+
use std::io::{BufReader, Write};
22+
23+
pub enum GraphOrMessage {
24+
Graph(Graph),
25+
Message(String),
26+
}
27+
28+
/// Converts JSON data to RDF format.
29+
///
30+
/// This function reads JSON data from the specified file, processes it into RDF triples,
31+
/// and outputs the RDF graph. Users can specify a namespace to use for RDF predicates and
32+
/// an output file for saving the generated RDF data.
33+
///
34+
/// # Arguments
35+
/// - `file_path`: Path to the JSON file.
36+
/// - `namespace`: Optional custom namespace for RDF predicates.
37+
/// - `output_file`: Optional output file path for writing RDF data.
38+
///
39+
/// # Example
40+
/// ```rust
41+
/// use json2rdf::json_to_rdf;
42+
///
43+
/// json_to_rdf(&"tests/airplane.json".to_string(), &Some("http://example.com/ns#".to_string()), &Some("output.nt".to_string()));
44+
/// ```
45+
46+
pub fn json_to_rdf(
47+
file_path: &String,
48+
namespace: &Option<String>,
49+
output_file: &Option<String>,
50+
) -> Result<GraphOrMessage, String> {
51+
let rdf_namespace: String = if namespace.is_some() {
52+
namespace.clone().unwrap()
53+
} else {
54+
"https://decisym.ai/json2rdf/model".to_owned()
55+
};
56+
57+
let file = File::open(file_path).unwrap();
58+
let reader = BufReader::new(file);
59+
let stream = Deserializer::from_reader(reader).into_iter::<Value>();
60+
61+
let mut graph = Graph::default(); // oxrdf Graph object
62+
63+
let mut subject_stack: VecDeque<BlankNode> = VecDeque::new();
64+
let mut property: Option<String> = None;
65+
66+
for value in stream {
67+
match value {
68+
Ok(Value::Object(obj)) => {
69+
let subject = BlankNode::default(); // Create a new blank node
70+
subject_stack.push_back(subject.clone());
71+
72+
for (key, val) in obj {
73+
property = Some(format!("{}/{}", rdf_namespace, key));
74+
process_value(
75+
&mut subject_stack,
76+
&property,
77+
val,
78+
&mut graph,
79+
&rdf_namespace,
80+
);
81+
}
82+
83+
subject_stack.pop_back();
84+
}
85+
Ok(Value::Array(arr)) => {
86+
for val in arr {
87+
process_value(
88+
&mut subject_stack,
89+
&property,
90+
val,
91+
&mut graph,
92+
&rdf_namespace.clone(),
93+
);
94+
}
95+
}
96+
Ok(other) => {
97+
process_value(
98+
&mut subject_stack,
99+
&property,
100+
other,
101+
&mut graph,
102+
&rdf_namespace.clone(),
103+
);
104+
}
105+
Err(e) => {
106+
eprintln!("Error parsing JSON: {}", e);
107+
}
108+
}
109+
}
110+
111+
if let Some(output_path) = output_file {
112+
let mut file = File::create(output_path).expect("Error creating file");
113+
writeln!(file, "{}", graph).expect("Error writing to file");
114+
Ok(GraphOrMessage::Message(format!(
115+
"RDF created at: {}",
116+
output_path
117+
)))
118+
} else {
119+
Ok(GraphOrMessage::Graph(graph))
120+
}
121+
}
122+
123+
/// This function handles different JSON data types, converting each into RDF triples:
124+
/// - JSON Objects create new blank nodes and recursively process nested values.
125+
/// - JSON Arrays iterate over each element and process it as an individual value.
126+
/// - JSON Booleans, Numbers, and Strings are converted to RDF literals.
127+
///
128+
/// # Recursion for Nested Structures
129+
/// Recursion is used to handle deeply nested JSON structures, which may contain multiple
130+
/// levels of objects or arrays. This recursive approach allows the function to "dive" into
131+
/// each nested layer of a JSON structure, creating blank nodes for sub-objects and handling
132+
/// them as new subjects within the RDF graph. As a result, each level of JSON data is
133+
/// systematically transformed into RDF triples, regardless of complexity or depth.
134+
///
135+
/// # Arguments
136+
/// - `subject_stack`: Stack of blank nodes representing subjects. Each nested level pushes a new subject to the stack.
137+
/// - `property`: RDF predicate (property) associated with the JSON value.
138+
/// - `value`: JSON value to process.
139+
/// - `graph`: RDF graph where triples are added.
140+
/// - `namespace`: Namespace for generating predicate URIs.
141+
///
142+
/// # JSON Type to RDF Conversion
143+
/// - **Object**: Creates a blank node and recursively processes key-value pairs.
144+
/// - **Array**: Iterates over elements and processes each as a separate value.
145+
/// - **String**: Converts to `xsd:string` literal.
146+
/// - **Boolean**: Converts to `xsd:boolean` literal.
147+
/// - **Number**: Converts to `xsd:int` or `xsd:float` literal based on value type.
148+
149+
fn process_value(
150+
subject_stack: &mut VecDeque<BlankNode>,
151+
property: &Option<String>,
152+
value: Value,
153+
graph: &mut Graph,
154+
namespace: &String,
155+
) {
156+
if let Some(last_subject) = subject_stack.clone().back() {
157+
if let Some(prop) = property {
158+
match value {
159+
Value::Bool(b) => {
160+
graph.insert(TripleRef::new(
161+
subject_stack.back().unwrap(),
162+
NamedNodeRef::new(prop.as_str()).unwrap(),
163+
&Literal::new_typed_literal(b.to_string(), xsd::BOOLEAN),
164+
));
165+
}
166+
Value::Number(num) => {
167+
let literal = if let Some(int) = num.as_i64() {
168+
int.to_string()
169+
} else if let Some(float) = num.as_f64() {
170+
float.to_string()
171+
} else {
172+
return;
173+
};
174+
graph.insert(TripleRef::new(
175+
subject_stack.back().unwrap(),
176+
NamedNodeRef::new(prop.as_str()).unwrap(),
177+
&Literal::new_typed_literal(literal, xsd::INT),
178+
));
179+
}
180+
Value::String(s) => {
181+
graph.insert(TripleRef::new(
182+
subject_stack.back().unwrap(),
183+
NamedNodeRef::new(prop.as_str()).unwrap(),
184+
&Literal::new_typed_literal(s, xsd::STRING),
185+
));
186+
}
187+
Value::Null => {
188+
//println!("Null value");
189+
}
190+
Value::Object(obj) => {
191+
let subject = BlankNode::default();
192+
subject_stack.push_back(subject);
193+
194+
graph.insert(TripleRef::new(
195+
last_subject,
196+
NamedNodeRef::new(prop.as_str()).unwrap(),
197+
subject_stack.back().unwrap(),
198+
));
199+
200+
for (key, val) in obj {
201+
let nested_property: Option<String> =
202+
Some(format!("{}/{}/", namespace, key));
203+
process_value(subject_stack, &nested_property, val, graph, namespace);
204+
}
205+
subject_stack.pop_back();
206+
}
207+
Value::Array(arr) => {
208+
for val in arr {
209+
process_value(subject_stack, property, val, graph, namespace);
210+
}
211+
}
212+
}
213+
}
214+
}
215+
}

0 commit comments

Comments
 (0)