1818//! - Allows specifying a custom RDF namespace for generated predicates and objects.
1919//! - Outputs the RDF data to a specified file or prints it to the console.
2020
21- use clap:: Error ;
2221use oxrdf:: vocab:: xsd;
23- use oxrdf:: { BlankNode , Graph , Literal , NamedNodeRef , TripleRef } ;
22+ use oxrdf:: { BlankNode , Graph , IriParseError , Literal , NamedNodeRef , TripleRef } ;
2423
2524use serde_json:: { Deserializer , Value } ;
2625use std:: collections:: VecDeque ;
2726use std:: fs:: { File , OpenOptions } ;
2827use std:: io:: { BufReader , Write } ;
28+ use thiserror:: Error ;
29+
30+ /// Errors that can occur while converting JSON to RDF.
31+ #[ derive( Debug , Error ) ]
32+ pub enum Json2RdfError {
33+ /// Failure opening, reading, or writing a file.
34+ #[ error( "I/O error: {0}" ) ]
35+ Io ( #[ from] std:: io:: Error ) ,
36+
37+ /// Failure parsing the input JSON.
38+ #[ error( "JSON parse error: {0}" ) ]
39+ Json ( #[ from] serde_json:: Error ) ,
40+
41+ /// A JSON key produced a string that is not a valid IRI.
42+ #[ error( "invalid IRI {iri:?} generated from JSON key: {source}" ) ]
43+ InvalidIri {
44+ iri : String ,
45+ #[ source]
46+ source : IriParseError ,
47+ } ,
48+ }
2949
3050/// Converts JSON data to RDF format.
3151///
@@ -38,24 +58,32 @@ use std::io::{BufReader, Write};
3858/// - `namespace`: Optional custom namespace for RDF predicates.
3959/// - `output_file`: Optional output file path for writing RDF data.
4060///
61+ /// # Errors
62+ /// Returns [`Json2RdfError`] if the input file cannot be read, the JSON cannot be parsed,
63+ /// the output file cannot be written, or a JSON key produces an invalid IRI.
64+ ///
4165/// # Example
4266/// ```rust
4367/// use json2rdf::json_to_rdf;
4468///
45- /// json_to_rdf(&"tests/airplane.json".to_string(), &Some("http://example.com/ns#".to_string()), &Some("output.nt".to_string()));
69+ /// json_to_rdf(
70+ /// &"tests/airplane.json".to_string(),
71+ /// &Some("http://example.com/ns#".to_string()),
72+ /// &Some("output.nt".to_string()),
73+ /// ).expect("conversion failed");
4674/// ```
4775pub fn json_to_rdf (
4876 file_path : & String ,
4977 namespace : & Option < String > ,
5078 output_file : & Option < String > ,
51- ) -> Result < Option < Graph > , Error > {
79+ ) -> Result < Option < Graph > , Json2RdfError > {
5280 let rdf_namespace: String = if namespace. is_some ( ) {
5381 namespace. clone ( ) . unwrap ( )
5482 } else {
5583 "https://decisym.ai/json2rdf/model" . to_owned ( )
5684 } ;
5785
58- let file = File :: open ( file_path) . unwrap ( ) ;
86+ let file = File :: open ( file_path) ? ;
5987 let reader = BufReader :: new ( file) ;
6088 let stream = Deserializer :: from_reader ( reader) . into_iter :: < Value > ( ) ;
6189
@@ -65,10 +93,11 @@ pub fn json_to_rdf(
6593 let mut property: Option < String > = None ;
6694
6795 for value in stream {
96+ let value = value?;
6897 match value {
69- Ok ( Value :: Object ( obj) ) => {
98+ Value :: Object ( obj) => {
7099 let subject = BlankNode :: default ( ) ; // Create a new blank node
71- subject_stack. push_back ( subject. clone ( ) ) ;
100+ subject_stack. push_back ( subject) ;
72101
73102 for ( key, val) in obj {
74103 property = Some ( format ! ( "{}/{}" , rdf_namespace, key) ) ;
@@ -78,33 +107,30 @@ pub fn json_to_rdf(
78107 val,
79108 & mut graph,
80109 & rdf_namespace,
81- ) ;
110+ ) ? ;
82111 }
83112
84113 subject_stack. pop_back ( ) ;
85114 }
86- Ok ( Value :: Array ( arr) ) => {
115+ Value :: Array ( arr) => {
87116 for val in arr {
88117 process_value (
89118 & mut subject_stack,
90119 & property,
91120 val,
92121 & mut graph,
93- & rdf_namespace. clone ( ) ,
94- ) ;
122+ & rdf_namespace,
123+ ) ? ;
95124 }
96125 }
97- Ok ( other) => {
126+ other => {
98127 process_value (
99128 & mut subject_stack,
100129 & property,
101130 other,
102131 & mut graph,
103- & rdf_namespace. clone ( ) ,
104- ) ;
105- }
106- Err ( e) => {
107- eprintln ! ( "Error parsing JSON: {}" , e) ;
132+ & rdf_namespace,
133+ ) ?;
108134 }
109135 }
110136 }
@@ -113,10 +139,9 @@ pub fn json_to_rdf(
113139 let mut file = OpenOptions :: new ( )
114140 . create ( true )
115141 . append ( true )
116- . open ( output_path)
117- . expect ( "Error opening file" ) ;
142+ . open ( output_path) ?;
118143
119- writeln ! ( file, "{}" , graph) . expect ( "Error writing json2rdf data to file" ) ;
144+ writeln ! ( file, "{}" , graph) ? ;
120145 Ok ( None )
121146 } else {
122147 Ok ( Some ( graph) )
@@ -147,77 +172,77 @@ pub fn json_to_rdf(
147172/// - **Array**: Iterates over elements and processes each as a separate value.
148173/// - **String**: Converts to `xsd:string` literal.
149174/// - **Boolean**: Converts to `xsd:boolean` literal.
150- /// - **Number**: Converts to `xsd:int` or `xsd:float` literal based on value type .
175+ /// - **Number**: Converts to `xsd:integer` for whole numbers, `xsd:double` for floating-point values .
151176fn process_value (
152177 subject_stack : & mut VecDeque < BlankNode > ,
153178 property : & Option < String > ,
154179 value : Value ,
155180 graph : & mut Graph ,
156181 namespace : & String ,
157- ) {
182+ ) -> Result < ( ) , Json2RdfError > {
158183 let ns = if namespace. ends_with ( "/" ) {
159184 namespace
160185 } else {
161186 & ( [ namespace, "/" ] . join ( "" ) )
162187 } ;
163188
164- if let Some ( last_subject) = subject_stack. clone ( ) . back ( ) {
165- if let Some ( prop) = property {
166- match value {
167- Value :: Bool ( b) => {
168- graph. insert ( TripleRef :: new (
169- subject_stack. back ( ) . unwrap ( ) ,
170- NamedNodeRef :: new ( prop. as_str ( ) ) . unwrap ( ) ,
171- & Literal :: new_typed_literal ( b. to_string ( ) , xsd:: BOOLEAN ) ,
172- ) ) ;
173- }
174- Value :: Number ( num) => {
175- if num. as_i64 ( ) . is_some ( ) {
176- graph. insert ( TripleRef :: new (
177- subject_stack. back ( ) . unwrap ( ) ,
178- NamedNodeRef :: new ( prop. as_str ( ) ) . unwrap ( ) ,
179- & Literal :: new_typed_literal ( num. to_string ( ) , xsd:: INT ) ,
180- ) ) ;
181- } else if num. as_f64 ( ) . is_some ( ) {
182- graph. insert ( TripleRef :: new (
183- subject_stack. back ( ) . unwrap ( ) ,
184- NamedNodeRef :: new ( prop. as_str ( ) ) . unwrap ( ) ,
185- & Literal :: new_typed_literal ( num. to_string ( ) , xsd:: FLOAT ) ,
186- ) ) ;
187- }
188- }
189- Value :: String ( s) => {
190- graph. insert ( TripleRef :: new (
191- subject_stack. back ( ) . unwrap ( ) ,
192- NamedNodeRef :: new ( prop. as_str ( ) ) . unwrap ( ) ,
193- & Literal :: new_typed_literal ( s, xsd:: STRING ) ,
194- ) ) ;
195- }
196- Value :: Null => {
197- //println!("Null value");
198- }
199- Value :: Object ( obj) => {
200- let subject = BlankNode :: default ( ) ;
201- subject_stack. push_back ( subject) ;
202-
203- graph. insert ( TripleRef :: new (
204- last_subject,
205- NamedNodeRef :: new ( prop. as_str ( ) ) . unwrap ( ) ,
206- subject_stack. back ( ) . unwrap ( ) ,
207- ) ) ;
208-
209- for ( key, val) in obj {
210- let nested_property: Option < String > = Some ( format ! ( "{}{}" , ns, key) ) ;
211- process_value ( subject_stack, & nested_property, val, graph, ns) ;
212- }
213- subject_stack. pop_back ( ) ;
214- }
215- Value :: Array ( arr) => {
216- for val in arr {
217- process_value ( subject_stack, property, val, graph, ns) ;
218- }
219- }
189+ let Some ( last_subject) = subject_stack. back ( ) . cloned ( ) else {
190+ return Ok ( ( ) ) ;
191+ } ;
192+ let Some ( prop) = property else {
193+ return Ok ( ( ) ) ;
194+ } ;
195+
196+ let predicate =
197+ NamedNodeRef :: new ( prop. as_str ( ) ) . map_err ( |source| Json2RdfError :: InvalidIri {
198+ iri : prop. clone ( ) ,
199+ source,
200+ } ) ?;
201+
202+ match value {
203+ Value :: Bool ( b) => {
204+ graph. insert ( TripleRef :: new (
205+ & last_subject,
206+ predicate,
207+ & Literal :: new_typed_literal ( b. to_string ( ) , xsd:: BOOLEAN ) ,
208+ ) ) ;
209+ }
210+ Value :: Number ( num) => {
211+ let datatype = if num. is_i64 ( ) || num. is_u64 ( ) {
212+ xsd:: INTEGER
213+ } else {
214+ xsd:: DOUBLE
215+ } ;
216+ graph. insert ( TripleRef :: new (
217+ & last_subject,
218+ predicate,
219+ & Literal :: new_typed_literal ( num. to_string ( ) , datatype) ,
220+ ) ) ;
221+ }
222+ Value :: String ( s) => {
223+ graph. insert ( TripleRef :: new (
224+ & last_subject,
225+ predicate,
226+ & Literal :: new_typed_literal ( s, xsd:: STRING ) ,
227+ ) ) ;
228+ }
229+ Value :: Null => { }
230+ Value :: Object ( obj) => {
231+ let new_subject = BlankNode :: default ( ) ;
232+ graph. insert ( TripleRef :: new ( & last_subject, predicate, & new_subject) ) ;
233+ subject_stack. push_back ( new_subject) ;
234+
235+ for ( key, val) in obj {
236+ let nested_property: Option < String > = Some ( format ! ( "{}{}" , ns, key) ) ;
237+ process_value ( subject_stack, & nested_property, val, graph, ns) ?;
238+ }
239+ subject_stack. pop_back ( ) ;
240+ }
241+ Value :: Array ( arr) => {
242+ for val in arr {
243+ process_value ( subject_stack, property, val, graph, ns) ?;
220244 }
221245 }
222246 }
247+ Ok ( ( ) )
223248}
0 commit comments