oxjsonld/
from_rdf.rs

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