Skip to main content

oxjsonld/
from_rdf.rs

1use crate::context::has_keyword_form;
2#[cfg(feature = "async-tokio")]
3use json_event_parser::TokioAsyncWriterJsonSerializer;
4use json_event_parser::{JsonEvent, WriterJsonSerializer};
5use oxiri::{Iri, IriParseError};
6#[cfg(feature = "rdf-12")]
7use oxrdf::BaseDirection;
8use oxrdf::vocab::xsd;
9use oxrdf::{
10    GraphName, GraphNameRef, NamedNode, NamedOrBlankNode, NamedOrBlankNodeRef, QuadRef, TermRef,
11};
12use std::borrow::Cow;
13use std::collections::{BTreeMap, BTreeSet};
14use std::io;
15use std::io::Write;
16#[cfg(feature = "async-tokio")]
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-tokio")]
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-tokio")]
296#[must_use]
297pub struct TokioAsyncWriterJsonLdSerializer<W: AsyncWrite + Unpin> {
298    writer: TokioAsyncWriterJsonSerializer<W>,
299    inner: InnerJsonLdWriter,
300}
301
302#[cfg(feature = "async-tokio")]
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.as_ref() != quad.graph_name)
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
369            .current_subject
370            .as_ref()
371            .is_some_and(|subject| subject.as_ref() != quad.subject)
372            || self
373                .current_predicate
374                .as_ref()
375                .is_some_and(|predicate| predicate.as_ref() != quad.predicate)
376                && self.emitted_predicates.contains(quad.predicate.as_str())
377        {
378            output.push(JsonEvent::EndArray);
379            output.push(JsonEvent::EndObject);
380            self.current_subject = None;
381            self.emitted_predicates.clear();
382            self.current_predicate = None;
383        } else if self
384            .current_predicate
385            .as_ref()
386            .is_some_and(|predicate| predicate.as_ref() != quad.predicate)
387        {
388            output.push(JsonEvent::EndArray);
389            if let Some(current_predicate) = self.current_predicate.take() {
390                self.emitted_predicates
391                    .insert(current_predicate.into_string());
392            }
393        }
394
395        if self.current_graph_name.is_none() {
396            if !quad.graph_name.is_default_graph() {
397                // We open a new graph name
398                output.push(JsonEvent::StartObject);
399                output.push(JsonEvent::ObjectKey("@id".into()));
400                output.push(JsonEvent::String(self.id_value(match quad.graph_name {
401                    GraphNameRef::NamedNode(iri) => iri.into(),
402                    GraphNameRef::BlankNode(bnode) => bnode.into(),
403                    GraphNameRef::DefaultGraph => unreachable!(),
404                })));
405                output.push(JsonEvent::ObjectKey("@graph".into()));
406                output.push(JsonEvent::StartArray);
407            }
408            self.current_graph_name = Some(quad.graph_name.into_owned());
409        }
410
411        // We open a new subject block if useful (ie. new subject or already used predicate)
412        if self.current_subject.is_none() {
413            output.push(JsonEvent::StartObject);
414            output.push(JsonEvent::ObjectKey("@id".into()));
415            #[allow(
416                unreachable_patterns,
417                clippy::match_wildcard_for_single_variants,
418                clippy::allow_attributes
419            )]
420            output.push(JsonEvent::String(self.id_value(match quad.subject {
421                NamedOrBlankNodeRef::NamedNode(iri) => iri.into(),
422                NamedOrBlankNodeRef::BlankNode(bnode) => bnode.into(),
423                _ => {
424                    return Err(io::Error::new(
425                        io::ErrorKind::InvalidInput,
426                        "JSON-LD does not support RDF 1.2 yet",
427                    ));
428                }
429            })));
430            self.current_subject = Some(quad.subject.into_owned());
431        }
432
433        // We open a predicate key
434        if self.current_predicate.is_none() {
435            output.push(JsonEvent::ObjectKey(
436                // TODO: use @type
437                quad.predicate.as_str().into(), // TODO: prefixes including @vocab
438            ));
439            output.push(JsonEvent::StartArray);
440            self.current_predicate = Some(quad.predicate.into_owned());
441        }
442
443        self.serialize_term(quad.object, output)
444    }
445
446    fn serialize_start(&self, output: &mut Vec<JsonEvent<'_>>) {
447        if self.base_iri.is_some() || !self.prefixes.is_empty() {
448            output.push(JsonEvent::StartObject);
449            output.push(JsonEvent::ObjectKey("@context".into()));
450            output.push(JsonEvent::StartObject);
451            if let Some(base_iri) = &self.base_iri {
452                output.push(JsonEvent::ObjectKey("@base".into()));
453                output.push(JsonEvent::String(base_iri.to_string().into()));
454            }
455            for (prefix_name, prefix_iri) in &self.prefixes {
456                output.push(JsonEvent::ObjectKey(if prefix_name.is_empty() {
457                    "@vocab".into()
458                } else {
459                    prefix_name.clone().into()
460                }));
461                output.push(JsonEvent::String(prefix_iri.clone().into()));
462            }
463            output.push(JsonEvent::EndObject);
464            output.push(JsonEvent::ObjectKey("@graph".into()));
465        }
466        output.push(JsonEvent::StartArray);
467    }
468
469    fn serialize_term<'a>(
470        &self,
471        term: TermRef<'a>,
472        output: &mut Vec<JsonEvent<'a>>,
473    ) -> io::Result<()> {
474        output.push(JsonEvent::StartObject);
475        #[allow(
476            unreachable_patterns,
477            clippy::match_wildcard_for_single_variants,
478            clippy::allow_attributes
479        )]
480        match term {
481            TermRef::NamedNode(iri) => {
482                output.push(JsonEvent::ObjectKey("@id".into()));
483                output.push(JsonEvent::String(self.id_value(iri.into())));
484            }
485            TermRef::BlankNode(bnode) => {
486                output.push(JsonEvent::ObjectKey("@id".into()));
487                output.push(JsonEvent::String(self.id_value(bnode.into())));
488            }
489            TermRef::Literal(literal) => {
490                if let Some(language) = literal.language() {
491                    output.push(JsonEvent::ObjectKey("@language".into()));
492                    output.push(JsonEvent::String(language.into()));
493                    #[cfg(feature = "rdf-12")]
494                    if let Some(direction) = literal.direction() {
495                        output.push(JsonEvent::ObjectKey("@direction".into()));
496                        output.push(JsonEvent::String(
497                            match direction {
498                                BaseDirection::Ltr => "ltr",
499                                BaseDirection::Rtl => "rtl",
500                            }
501                            .into(),
502                        ));
503                    }
504                } else if literal.datatype() != xsd::STRING {
505                    output.push(JsonEvent::ObjectKey("@type".into()));
506                    output.push(JsonEvent::String(Self::type_value(
507                        literal.datatype().into(),
508                    )));
509                }
510                output.push(JsonEvent::ObjectKey("@value".into()));
511                output.push(JsonEvent::String(literal.value().into()));
512            }
513            _ => {
514                return Err(io::Error::new(
515                    io::ErrorKind::InvalidInput,
516                    "JSON-LD does not support RDF 1.2 yet",
517                ));
518            }
519        }
520        output.push(JsonEvent::EndObject);
521        Ok(())
522    }
523
524    fn id_value<'a>(&self, id: NamedOrBlankNodeRef<'a>) -> Cow<'a, str> {
525        match id {
526            NamedOrBlankNodeRef::NamedNode(iri) => {
527                if let Some(base_iri) = &self.base_iri {
528                    if let Ok(relative) = base_iri.relativize(&Iri::parse_unchecked(iri.as_str())) {
529                        let relative = relative.into_inner();
530                        // We check the relative IRI is not considered as absolute or a keyword by IRI expansion
531                        if !relative.split_once(':').is_some_and(|(prefix, suffix)| {
532                            prefix == "_" || suffix.starts_with("//")
533                        }) && !has_keyword_form(&relative)
534                        {
535                            return relative.into();
536                        }
537                    }
538                }
539                iri.as_str().into()
540            }
541            NamedOrBlankNodeRef::BlankNode(bnode) => bnode.to_string().into(),
542        }
543    }
544
545    fn type_value(id: NamedOrBlankNodeRef<'_>) -> Cow<'_, str> {
546        match id {
547            NamedOrBlankNodeRef::NamedNode(iri) => iri.as_str().into(),
548            NamedOrBlankNodeRef::BlankNode(bnode) => bnode.to_string().into(),
549        }
550    }
551
552    fn finish(&mut self, output: &mut Vec<JsonEvent<'static>>) {
553        if !self.started {
554            self.serialize_start(output);
555        }
556        if self.current_predicate.is_some() {
557            output.push(JsonEvent::EndArray)
558        }
559        if self.current_subject.is_some() {
560            output.push(JsonEvent::EndObject)
561        }
562        if self
563            .current_graph_name
564            .as_ref()
565            .is_some_and(|g| !g.is_default_graph())
566        {
567            output.push(JsonEvent::EndArray);
568            output.push(JsonEvent::EndObject)
569        }
570        output.push(JsonEvent::EndArray);
571        if self.base_iri.is_some() || !self.prefixes.is_empty() {
572            output.push(JsonEvent::EndObject);
573        }
574    }
575}