oxirs_core/jsonld/from_rdf.rs
1#[cfg(feature = "async")]
2use json_event_parser::TokioAsyncWriterJsonSerializer;
3use json_event_parser::{JsonEvent, WriterJsonSerializer};
4// Native OxiRS types (Phase 3 migration completed)
5use crate::model::node::NamedOrBlankNodeRef;
6use crate::model::quad::GraphNameRef;
7use crate::model::triple::{ObjectRef, PredicateRef, SubjectRef};
8use crate::model::*;
9use crate::optimization::TermRef;
10use crate::vocab::xsd;
11use oxiri::{Iri, IriParseError};
12use std::borrow::Cow;
13use std::collections::{BTreeMap, BTreeSet};
14use std::io;
15use std::io::Write;
16#[cfg(feature = "async")]
17use tokio::io::AsyncWrite;
18
19/// A [JSON-LD](https://www.w3.org/TR/json-ld/) serializer.
20///
21/// Returns [Streaming JSON-LD](https://www.w3.org/TR/json-ld11-streaming/).
22///
23/// It does not implement exactly the [RDF as JSON-LD Algorithm](https://www.w3.org/TR/json-ld-api/#serialize-rdf-as-json-ld-algorithm)
24/// to be a streaming serializer but aims at being close to it.
25/// Features like `@json` and `@list` generation are not implemented.
26///
27/// ```
28/// use oxrdf::{GraphNameRef, LiteralRef, NamedNodeRef, QuadRef};
29/// use oxrdf::vocab::rdf;
30/// use oxjsonld::JsonLdSerializer;
31///
32/// let mut serializer = JsonLdSerializer::new().with_prefix("schema", "http://schema.org/")?.for_writer(Vec::new());
33/// serializer.serialize_quad(QuadRef::new(
34/// NamedNodeRef::new("http://example.com#me")?,
35/// rdf::TYPE,
36/// NamedNodeRef::new("http://schema.org/Person")?,
37/// GraphNameRef::DefaultGraph
38/// ))?;
39/// serializer.serialize_quad(QuadRef::new(
40/// NamedNodeRef::new("http://example.com#me")?,
41/// NamedNodeRef::new("http://schema.org/name")?,
42/// LiteralRef::new_language_tagged_literal_unchecked("Foo Bar", "en"),
43/// GraphNameRef::DefaultGraph
44/// ))?;
45/// assert_eq!(
46/// b"{\"@context\":{\"schema\":\"http://schema.org/\"},\"@graph\":[{\"@id\":\"http://example.com#me\",\"http://www.w3.org/1999/02/22-rdf-syntax-ns#type\":[{\"@id\":\"http://schema.org/Person\"}],\"http://schema.org/name\":[{\"@language\":\"en\",\"@value\":\"Foo Bar\"}]}]}",
47/// serializer.finish()?.as_slice()
48/// );
49/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
50/// ```
51#[derive(Default, Clone)]
52#[must_use]
53pub struct JsonLdSerializer {
54 prefixes: BTreeMap<String, String>,
55 base_iri: Option<Iri<String>>,
56}
57
58impl JsonLdSerializer {
59 /// Builds a new [`JsonLdSerializer`].
60 #[inline]
61 pub fn new() -> Self {
62 Self {
63 prefixes: BTreeMap::new(),
64 base_iri: None,
65 }
66 }
67
68 #[inline]
69 pub fn with_prefix(
70 mut self,
71 prefix_name: impl Into<String>,
72 prefix_iri: impl Into<String>,
73 ) -> Result<Self, IriParseError> {
74 self.prefixes.insert(
75 prefix_name.into(),
76 Iri::parse(prefix_iri.into())?.into_inner(),
77 );
78 Ok(self)
79 }
80
81 /// Allows to set the base IRI for serialization.
82 ///
83 /// Corresponds to the [`base` option from the algorithm specification](https://www.w3.org/TR/json-ld-api/#dom-jsonldoptions-base).
84 /// ```
85 /// use oxrdf::{GraphNameRef, NamedNodeRef, QuadRef};
86 /// use oxjsonld::JsonLdSerializer;
87 ///
88 /// let mut serializer = JsonLdSerializer::new()
89 /// .with_base_iri("http://example.com")?
90 /// .with_prefix("ex", "http://example.com/ns#")?
91 /// .for_writer(Vec::new());
92 /// serializer.serialize_quad(QuadRef::new(
93 /// NamedNodeRef::new("http://example.com#me")?,
94 /// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
95 /// NamedNodeRef::new("http://example.com/ns#Person")?,
96 /// GraphNameRef::DefaultGraph
97 /// ))?;
98 /// serializer.serialize_quad(QuadRef::new(
99 /// NamedNodeRef::new("http://example.com#me")?,
100 /// NamedNodeRef::new("http://example.com/ns#parent")?,
101 /// NamedNodeRef::new("http://example.com#other")?,
102 /// GraphNameRef::DefaultGraph
103 /// ))?;
104 /// assert_eq!(
105 /// b"{\"@context\":{\"@base\":\"http://example.com\",\"ex\":\"http://example.com/ns#\"},\"@graph\":[{\"@id\":\"#me\",\"http://www.w3.org/1999/02/22-rdf-syntax-ns#type\":[{\"@id\":\"/ns#Person\"}],\"http://example.com/ns#parent\":[{\"@id\":\"#other\"}]}]}",
106 /// serializer.finish()?.as_slice()
107 /// );
108 /// # Result::<_,Box<dyn std::error::Error>>::Ok(())
109 /// ```
110 #[inline]
111 pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Result<Self, IriParseError> {
112 self.base_iri = Some(Iri::parse(base_iri.into())?);
113 Ok(self)
114 }
115
116 /// Serializes a JSON-LD file to a [`Write`] implementation.
117 ///
118 /// This writer does unbuffered writes.
119 ///
120 /// ```
121 /// use oxrdf::{GraphNameRef, LiteralRef, NamedNodeRef, QuadRef};
122 /// use oxrdf::vocab::rdf;
123 /// use oxjsonld::JsonLdSerializer;
124 ///
125 /// let mut serializer = JsonLdSerializer::new().with_prefix("schema", "http://schema.org/")?.for_writer(Vec::new());
126 /// serializer.serialize_quad(QuadRef::new(
127 /// NamedNodeRef::new("http://example.com#me")?,
128 /// rdf::TYPE,
129 /// NamedNodeRef::new("http://schema.org/Person")?,
130 /// GraphNameRef::DefaultGraph
131 /// ))?;
132 /// serializer.serialize_quad(QuadRef::new(
133 /// NamedNodeRef::new("http://example.com#me")?,
134 /// NamedNodeRef::new("http://schema.org/name")?,
135 /// LiteralRef::new_language_tagged_literal_unchecked("Foo Bar", "en"),
136 /// GraphNameRef::DefaultGraph
137 /// ))?;
138 /// assert_eq!(
139 /// b"{\"@context\":{\"schema\":\"http://schema.org/\"},\"@graph\":[{\"@id\":\"http://example.com#me\",\"http://www.w3.org/1999/02/22-rdf-syntax-ns#type\":[{\"@id\":\"http://schema.org/Person\"}],\"http://schema.org/name\":[{\"@language\":\"en\",\"@value\":\"Foo Bar\"}]}]}",
140 /// serializer.finish()?.as_slice()
141 /// );
142 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
143 /// ```
144 pub fn for_writer<W: Write>(self, writer: W) -> WriterJsonLdSerializer<W> {
145 WriterJsonLdSerializer {
146 writer: WriterJsonSerializer::new(writer),
147 inner: self.inner_writer(),
148 }
149 }
150
151 /// Serializes a JSON-LD file to a [`AsyncWrite`] implementation.
152 ///
153 /// This writer does unbuffered writes.
154 ///
155 /// ```
156 /// # #[tokio::main(flavor = "current_thread")]
157 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
158 /// use oxrdf::{NamedNodeRef, QuadRef, LiteralRef, GraphNameRef};
159 /// use oxrdf::vocab::rdf;
160 /// use oxjsonld::JsonLdSerializer;
161 ///
162 /// let mut serializer = JsonLdSerializer::new().with_prefix("schema", "http://schema.org/")?.for_tokio_async_writer(Vec::new());
163 /// serializer.serialize_quad(QuadRef::new(
164 /// NamedNodeRef::new("http://example.com#me")?,
165 /// rdf::TYPE,
166 /// NamedNodeRef::new("http://schema.org/Person")?,
167 /// GraphNameRef::DefaultGraph
168 /// )).await?;
169 /// serializer.serialize_quad(QuadRef::new(
170 /// NamedNodeRef::new("http://example.com#me")?,
171 /// NamedNodeRef::new("http://schema.org/name")?,
172 /// LiteralRef::new_language_tagged_literal_unchecked("Foo Bar", "en"),
173 /// GraphNameRef::DefaultGraph
174 /// )).await?;
175 /// assert_eq!(
176 /// b"{\"@context\":{\"schema\":\"http://schema.org/\"},\"@graph\":[{\"@id\":\"http://example.com#me\",\"http://www.w3.org/1999/02/22-rdf-syntax-ns#type\":[{\"@id\":\"http://schema.org/Person\"}],\"http://schema.org/name\":[{\"@language\":\"en\",\"@value\":\"Foo Bar\"}]}]}",
177 /// serializer.finish().await?.as_slice()
178 /// );
179 /// # Ok(())
180 /// # }
181 /// ```
182 #[cfg(feature = "async")]
183 pub fn for_tokio_async_writer<W: AsyncWrite + Unpin>(
184 self,
185 writer: W,
186 ) -> TokioAsyncWriterJsonLdSerializer<W> {
187 TokioAsyncWriterJsonLdSerializer {
188 writer: TokioAsyncWriterJsonSerializer::new(writer),
189 inner: self.inner_writer(),
190 }
191 }
192
193 fn inner_writer(self) -> InnerJsonLdWriter {
194 InnerJsonLdWriter {
195 started: false,
196 current_graph_name: None,
197 current_subject: None,
198 current_predicate: None,
199 emitted_predicates: BTreeSet::new(),
200 prefixes: self.prefixes,
201 base_iri: self.base_iri,
202 }
203 }
204}
205
206/// Serializes a JSON-LD file to a [`Write`] implementation.
207///
208/// Can be built using [`JsonLdSerializer::for_writer`].
209///
210/// ```
211/// use oxrdf::{GraphNameRef, LiteralRef, NamedNodeRef, QuadRef};
212/// use oxrdf::vocab::rdf;
213/// use oxjsonld::JsonLdSerializer;
214///
215/// let mut serializer = JsonLdSerializer::new().with_prefix("schema", "http://schema.org/")?.for_writer(Vec::new());
216/// serializer.serialize_quad(QuadRef::new(
217/// NamedNodeRef::new("http://example.com#me")?,
218/// rdf::TYPE,
219/// NamedNodeRef::new("http://schema.org/Person")?,
220/// GraphNameRef::DefaultGraph
221/// ))?;
222/// serializer.serialize_quad(QuadRef::new(
223/// NamedNodeRef::new("http://example.com#me")?,
224/// NamedNodeRef::new("http://schema.org/name")?,
225/// LiteralRef::new_language_tagged_literal_unchecked("Foo Bar", "en"),
226/// GraphNameRef::DefaultGraph
227/// ))?;
228/// assert_eq!(
229/// b"{\"@context\":{\"schema\":\"http://schema.org/\"},\"@graph\":[{\"@id\":\"http://example.com#me\",\"http://www.w3.org/1999/02/22-rdf-syntax-ns#type\":[{\"@id\":\"http://schema.org/Person\"}],\"http://schema.org/name\":[{\"@language\":\"en\",\"@value\":\"Foo Bar\"}]}]}",
230/// serializer.finish()?.as_slice()
231/// );
232/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
233/// ```
234#[must_use]
235pub struct WriterJsonLdSerializer<W: Write> {
236 writer: WriterJsonSerializer<W>,
237 inner: InnerJsonLdWriter,
238}
239
240impl<W: Write> WriterJsonLdSerializer<W> {
241 /// Serializes an extra quad.
242 pub fn serialize_quad<'a>(&mut self, t: impl Into<QuadRef<'a>>) -> io::Result<()> {
243 let mut buffer = Vec::new();
244 self.inner.serialize_quad(t, &mut buffer)?;
245 self.flush_buffer(&mut buffer)
246 }
247
248 /// Ends the write process and returns the underlying [`Write`].
249 pub fn finish(mut self) -> io::Result<W> {
250 let mut buffer = Vec::new();
251 self.inner.finish(&mut buffer);
252 self.flush_buffer(&mut buffer)?;
253 self.writer.finish()
254 }
255
256 fn flush_buffer(&mut self, buffer: &mut Vec<JsonEvent<'_>>) -> io::Result<()> {
257 for event in buffer.drain(0..) {
258 self.writer.serialize_event(event)?;
259 }
260 Ok(())
261 }
262}
263
264/// Serializes a JSON-LD file to a [`AsyncWrite`] implementation.
265///
266/// Can be built using [`JsonLdSerializer::for_tokio_async_writer`].
267///
268/// ```
269/// # #[tokio::main(flavor = "current_thread")]
270/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
271/// use oxrdf::{NamedNodeRef, QuadRef, LiteralRef, GraphNameRef};
272/// use oxrdf::vocab::rdf;
273/// use oxjsonld::JsonLdSerializer;
274///
275/// let mut serializer = JsonLdSerializer::new().with_prefix("schema", "http://schema.org/")?.for_tokio_async_writer(Vec::new());
276/// serializer.serialize_quad(QuadRef::new(
277/// NamedNodeRef::new("http://example.com#me")?,
278/// rdf::TYPE,
279/// NamedNodeRef::new("http://schema.org/Person")?,
280/// GraphNameRef::DefaultGraph
281/// )).await?;
282/// serializer.serialize_quad(QuadRef::new(
283/// NamedNodeRef::new("http://example.com#me")?,
284/// NamedNodeRef::new("http://schema.org/name")?,
285/// LiteralRef::new_language_tagged_literal_unchecked("Foo Bar", "en"),
286/// GraphNameRef::DefaultGraph
287/// )).await?;
288/// assert_eq!(
289/// b"{\"@context\":{\"schema\":\"http://schema.org/\"},\"@graph\":[{\"@id\":\"http://example.com#me\",\"http://www.w3.org/1999/02/22-rdf-syntax-ns#type\":[{\"@id\":\"http://schema.org/Person\"}],\"http://schema.org/name\":[{\"@language\":\"en\",\"@value\":\"Foo Bar\"}]}]}",
290/// serializer.finish().await?.as_slice()
291/// );
292/// # Ok(())
293/// # }
294/// ```
295#[cfg(feature = "async")]
296#[must_use]
297pub struct TokioAsyncWriterJsonLdSerializer<W: AsyncWrite + Unpin> {
298 writer: TokioAsyncWriterJsonSerializer<W>,
299 inner: InnerJsonLdWriter,
300}
301
302#[cfg(feature = "async")]
303impl<W: AsyncWrite + Unpin> TokioAsyncWriterJsonLdSerializer<W> {
304 /// Serializes an extra quad.
305 pub async fn serialize_quad<'a>(&mut self, t: impl Into<QuadRef<'a>>) -> io::Result<()> {
306 let mut buffer = Vec::new();
307 self.inner.serialize_quad(t, &mut buffer)?;
308 self.flush_buffer(&mut buffer).await
309 }
310
311 /// Ends the write process and returns the underlying [`Write`].
312 pub async fn finish(mut self) -> io::Result<W> {
313 let mut buffer = Vec::new();
314 self.inner.finish(&mut buffer);
315 self.flush_buffer(&mut buffer).await?;
316 self.writer.finish()
317 }
318
319 async fn flush_buffer(&mut self, buffer: &mut Vec<JsonEvent<'_>>) -> io::Result<()> {
320 for event in buffer.drain(0..) {
321 self.writer.serialize_event(event).await?;
322 }
323 Ok(())
324 }
325}
326
327pub struct InnerJsonLdWriter {
328 started: bool,
329 current_graph_name: Option<GraphName>,
330 current_subject: Option<NamedOrBlankNode>,
331 current_predicate: Option<NamedNode>,
332 emitted_predicates: BTreeSet<String>,
333 prefixes: BTreeMap<String, String>,
334 base_iri: Option<Iri<String>>,
335}
336
337impl InnerJsonLdWriter {
338 fn serialize_quad<'a>(
339 &mut self,
340 quad: impl Into<QuadRef<'a>>,
341 output: &mut Vec<JsonEvent<'a>>,
342 ) -> io::Result<()> {
343 if !self.started {
344 self.serialize_start(output);
345 self.started = true;
346 }
347
348 let quad = quad.into();
349 if self
350 .current_graph_name
351 .as_ref()
352 .is_some_and(|graph_name| graph_name != &quad.graph_name().to_owned())
353 {
354 output.push(JsonEvent::EndArray);
355 output.push(JsonEvent::EndObject);
356 if self
357 .current_graph_name
358 .as_ref()
359 .is_some_and(|g| !g.is_default_graph())
360 {
361 output.push(JsonEvent::EndArray);
362 output.push(JsonEvent::EndObject);
363 }
364 self.current_graph_name = None;
365 self.current_subject = None;
366 self.current_predicate = None;
367 self.emitted_predicates.clear();
368 } else if self.current_subject.as_ref().is_some_and(|subject| {
369 match quad.subject() {
370 SubjectRef::NamedNode(n) => subject != &NamedOrBlankNode::NamedNode(n.to_owned()),
371 SubjectRef::BlankNode(b) => subject != &NamedOrBlankNode::BlankNode(b.to_owned()),
372 _ => true, // Variable and QuotedTriple are not supported in JSON-LD
373 }
374 }) || self.current_predicate.as_ref().is_some_and(|predicate| {
375 match quad.predicate() {
376 PredicateRef::NamedNode(n) => predicate != &n.to_owned(),
377 _ => true, // Variable predicates are not supported in JSON-LD
378 }
379 }) && {
380 let pred_str = match quad.predicate() {
381 PredicateRef::NamedNode(n) => n.as_str(),
382 PredicateRef::Variable(v) => v.as_str(),
383 };
384 self.emitted_predicates.contains(pred_str)
385 } {
386 output.push(JsonEvent::EndArray);
387 output.push(JsonEvent::EndObject);
388 self.current_subject = None;
389 self.emitted_predicates.clear();
390 self.current_predicate = None;
391 } else if self.current_predicate.as_ref().is_some_and(|predicate| {
392 match quad.predicate() {
393 PredicateRef::NamedNode(n) => predicate != &n.to_owned(),
394 _ => true, // Variable predicates are not supported in JSON-LD
395 }
396 }) {
397 output.push(JsonEvent::EndArray);
398 if let Some(current_predicate) = self.current_predicate.take() {
399 self.emitted_predicates
400 .insert(current_predicate.into_string());
401 }
402 }
403
404 if self.current_graph_name.is_none() {
405 if !quad.graph_name().is_default_graph() {
406 // We open a new graph name
407 output.push(JsonEvent::StartObject);
408 output.push(JsonEvent::ObjectKey("@id".into()));
409 output.push(JsonEvent::String(self.id_value(match quad.graph_name() {
410 GraphNameRef::NamedNode(iri) => NamedOrBlankNodeRef::NamedNode(iri),
411 GraphNameRef::BlankNode(bnode) => NamedOrBlankNodeRef::BlankNode(bnode),
412 GraphNameRef::DefaultGraph => unreachable!(),
413 GraphNameRef::Variable(_) => unreachable!(), // Variables shouldn't appear in actual data
414 })));
415 output.push(JsonEvent::ObjectKey("@graph".into()));
416 output.push(JsonEvent::StartArray);
417 }
418 self.current_graph_name = Some(quad.graph_name().to_owned());
419 }
420
421 // We open a new subject block if useful (ie. new subject or already used predicate)
422 if self.current_subject.is_none() {
423 output.push(JsonEvent::StartObject);
424 output.push(JsonEvent::ObjectKey("@id".into()));
425 #[allow(
426 unreachable_patterns,
427 clippy::match_wildcard_for_single_variants,
428 clippy::allow_attributes
429 )]
430 output.push(JsonEvent::String(self.id_value(match quad.subject() {
431 SubjectRef::NamedNode(iri) => NamedOrBlankNodeRef::NamedNode(iri),
432 SubjectRef::BlankNode(bnode) => NamedOrBlankNodeRef::BlankNode(bnode),
433 _ => {
434 return Err(io::Error::new(
435 io::ErrorKind::InvalidInput,
436 "JSON-LD does not support RDF 1.2 yet",
437 ));
438 }
439 })));
440 self.current_subject = Some(match quad.subject() {
441 SubjectRef::NamedNode(n) => NamedOrBlankNode::NamedNode(n.to_owned()),
442 SubjectRef::BlankNode(b) => NamedOrBlankNode::BlankNode(b.to_owned()),
443 _ => {
444 return Err(io::Error::new(
445 io::ErrorKind::InvalidInput,
446 "JSON-LD does not support variables or quoted triples as subjects",
447 ));
448 }
449 });
450 }
451
452 // We open a predicate key
453 if self.current_predicate.is_none() {
454 let predicate_str = match quad.predicate() {
455 PredicateRef::NamedNode(n) => {
456 // Apply prefix compaction if available
457 // Note: @type shorthand requires special object serialization (plain IRIs)
458 // which is not yet implemented, so we use full IRI with prefix compaction
459 self.compact_iri(n.as_str())
460 }
461 PredicateRef::Variable(v) => Cow::Borrowed(v.as_str()),
462 };
463 output.push(JsonEvent::ObjectKey(predicate_str));
464 output.push(JsonEvent::StartArray);
465 self.current_predicate = Some(match quad.predicate() {
466 PredicateRef::NamedNode(n) => n.to_owned(),
467 _ => {
468 return Err(io::Error::new(
469 io::ErrorKind::InvalidInput,
470 "JSON-LD does not support variables as predicates",
471 ));
472 }
473 });
474 }
475
476 let object_ref = match quad.object() {
477 ObjectRef::NamedNode(n) => TermRef::NamedNode(n.as_str()),
478 ObjectRef::BlankNode(b) => TermRef::BlankNode(b.as_str()),
479 ObjectRef::Literal(l) => TermRef::from_literal(l),
480 ObjectRef::Variable(v) => TermRef::Variable(v.as_str()),
481 };
482 self.serialize_term(object_ref, output)
483 }
484
485 fn serialize_start(&self, output: &mut Vec<JsonEvent<'_>>) {
486 if self.base_iri.is_some() || !self.prefixes.is_empty() {
487 output.push(JsonEvent::StartObject);
488 output.push(JsonEvent::ObjectKey("@context".into()));
489 output.push(JsonEvent::StartObject);
490 if let Some(base_iri) = &self.base_iri {
491 output.push(JsonEvent::ObjectKey("@base".into()));
492 output.push(JsonEvent::String(base_iri.to_string().into()));
493 }
494 for (prefix_name, prefix_iri) in &self.prefixes {
495 output.push(JsonEvent::ObjectKey(if prefix_name.is_empty() {
496 "@vocab".into()
497 } else {
498 prefix_name.clone().into()
499 }));
500 output.push(JsonEvent::String(prefix_iri.clone().into()));
501 }
502 output.push(JsonEvent::EndObject);
503 output.push(JsonEvent::ObjectKey("@graph".into()));
504 }
505 output.push(JsonEvent::StartArray);
506 }
507
508 fn serialize_term<'a>(
509 &self,
510 term: TermRef<'a>,
511 output: &mut Vec<JsonEvent<'a>>,
512 ) -> io::Result<()> {
513 output.push(JsonEvent::StartObject);
514 #[allow(
515 unreachable_patterns,
516 clippy::match_wildcard_for_single_variants,
517 clippy::allow_attributes
518 )]
519 match term {
520 TermRef::NamedNode(iri) => {
521 output.push(JsonEvent::ObjectKey("@id".into()));
522 // For named nodes, we can use the IRI directly with base IRI processing
523 output.push(JsonEvent::String(self.id_value_from_str(iri, false)));
524 }
525 TermRef::BlankNode(bnode) => {
526 output.push(JsonEvent::ObjectKey("@id".into()));
527 // For blank nodes, we need to add the "_:" prefix
528 output.push(JsonEvent::String(self.id_value_from_str(bnode, true)));
529 }
530 TermRef::Literal(value, datatype, language) => {
531 if let Some(lang) = language {
532 output.push(JsonEvent::ObjectKey("@language".into()));
533 output.push(JsonEvent::String(lang.into()));
534 } else if let Some(dt) = datatype {
535 if dt != xsd::STRING.as_str() {
536 output.push(JsonEvent::ObjectKey("@type".into()));
537 // For datatypes, use the IRI directly
538 output.push(JsonEvent::String(dt.into()));
539 }
540 }
541 output.push(JsonEvent::ObjectKey("@value".into()));
542 output.push(JsonEvent::String(value.into()));
543 }
544 _ => {
545 return Err(io::Error::new(
546 io::ErrorKind::InvalidInput,
547 "JSON-LD does not support RDF 1.2 yet",
548 ));
549 }
550 }
551 output.push(JsonEvent::EndObject);
552 Ok(())
553 }
554
555 fn id_value<'a>(&self, id: NamedOrBlankNodeRef<'a>) -> Cow<'a, str> {
556 match id {
557 NamedOrBlankNodeRef::NamedNode(iri) => {
558 if let Some(base_iri) = &self.base_iri {
559 if let Ok(relative) = base_iri.relativize(&Iri::parse_unchecked(iri.as_str())) {
560 let relative = relative.into_inner();
561 // We check the relative IRI is not considered as absolute by IRI expansion
562 if !relative.split_once(':').is_some_and(|(prefix, suffix)| {
563 prefix == "_" || suffix.starts_with("//")
564 }) {
565 return relative.into();
566 }
567 }
568 }
569 iri.as_str().into()
570 }
571 NamedOrBlankNodeRef::BlankNode(bnode) => bnode.to_string().into(),
572 }
573 }
574
575 fn id_value_from_str<'a>(&self, id: &'a str, is_blank_node: bool) -> Cow<'a, str> {
576 if is_blank_node {
577 // For blank nodes, add the "_:" prefix
578 format!("_:{id}").into()
579 } else {
580 // For IRIs, apply base IRI relativization
581 if let Some(base_iri) = &self.base_iri {
582 if let Ok(relative) = base_iri.relativize(&Iri::parse_unchecked(id)) {
583 let relative = relative.into_inner();
584 // We check the relative IRI is not considered as absolute by IRI expansion
585 if !relative
586 .split_once(':')
587 .is_some_and(|(prefix, suffix)| prefix == "_" || suffix.starts_with("//"))
588 {
589 return relative.into();
590 }
591 }
592 }
593 id.into()
594 }
595 }
596
597 #[allow(dead_code)]
598 fn type_value(id: NamedOrBlankNodeRef<'_>) -> Cow<'_, str> {
599 match id {
600 NamedOrBlankNodeRef::NamedNode(iri) => iri.as_str().into(),
601 NamedOrBlankNodeRef::BlankNode(bnode) => bnode.to_string().into(),
602 }
603 }
604
605 /// Compact an IRI using registered prefixes
606 ///
607 /// This applies JSON-LD prefix compaction to transform full IRIs into
608 /// compact prefix:localname form when a matching prefix is registered.
609 fn compact_iri<'a>(&self, iri: &'a str) -> Cow<'a, str> {
610 // Try to find a matching prefix for compaction
611 for (prefix, namespace) in &self.prefixes {
612 if let Some(local_name) = iri.strip_prefix(namespace.as_str()) {
613 // Found a matching prefix - return compact form
614 return Cow::Owned(format!("{prefix}:{local_name}"));
615 }
616 }
617 // No matching prefix found - return original IRI
618 Cow::Borrowed(iri)
619 }
620
621 fn finish(&mut self, output: &mut Vec<JsonEvent<'static>>) {
622 if !self.started {
623 self.serialize_start(output);
624 }
625 if self.current_predicate.is_some() {
626 output.push(JsonEvent::EndArray)
627 }
628 if self.current_subject.is_some() {
629 output.push(JsonEvent::EndObject)
630 }
631 if self
632 .current_graph_name
633 .as_ref()
634 .is_some_and(|g| !g.is_default_graph())
635 {
636 output.push(JsonEvent::EndArray);
637 output.push(JsonEvent::EndObject)
638 }
639 output.push(JsonEvent::EndArray);
640 if self.base_iri.is_some() || !self.prefixes.is_empty() {
641 output.push(JsonEvent::EndObject);
642 }
643 }
644}