Jackson Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

JSON-LD is a JSON-based RDF format for representing Linked Data. It enables extending existing JSON objects with hypermedia capabilities; in other words, the capability to contain links in a machine-readable way.

In this tutorial, we'll look at a couple of Jackson-based options to serialize and deserialize the JSON-LD format directly into POJOs. We'll also cover the basic concepts of JSON-LD that will enable us to understand the examples.

2. Basic Concepts

The first time we see a JSON-LD document, we notice that some member names start with the @ character. These are JSON-LD keywords, and their values help us to understand the rest of the document.

To navigate the world of JSON-LD and to understand this tutorial, we need to be aware of four keywords:

  • @context is the description of the JSON object that contains a key-value map of everything needed for the interpretation of the document
  • @vocab is a possible key in @context that introduces a default vocabulary to make the @context object much shorter
  • @id is the keyword to identify links either as a resource property to represent the direct link to the resource itself or as a @type value to mark any field as a link
  • @type is the keyword to identify resource types either on the resource level or in the @context; for example, to define the type of embedded resources

3. Serialization in Java

Before we continue, we should take a look at our previous tutorials to refresh our memory on the Jackson ObjectMapper, Jackson Annotations, and custom Jackson Serializers.

Being already familiar with Jackson, we might realize that we could easily serialize two custom fields in any POJO as @id and @type using the @JsonProperty annotation. However, writing the @context by hand could be a lot of work and also prone to error.

Therefore, to avoid this error-prone approach, let's take a closer look at two libraries that we could use for @context generation. Unfortunately, neither one of them is capable of generating all features of JSON-LD, but we'll take a look at their shortcomings later as well.

4. Serialization With Jackson-Jsonld

Jackson-Jsonld is a Jackson module that enables the annotation of POJOs in a convenient way to generate JSON-LD documents.

4.1. Maven Dependencies

First, let's add jackson-jsonld as a dependency to the pom.xml:

<dependency>
    <groupId>com.io-informatics.oss</groupId>
    <artifactId>jackson-jsonld</artifactId>
    <version>0.1.1</version>
</dependency>

4.2. Example

Then, let's create our example POJO and annotate it for @context generation:

@JsonldResource
@JsonldNamespace(name = "s", uri = "http://schema.org/")
@JsonldType("s:Person")
@JsonldLink(rel = "s:knows", name = "knows", href = "http://example.com/person/2345")
public class Person {
    @JsonldId
    private String id;
    @JsonldProperty("s:name")
    private String name;

    // constructor, getters, setters
}

Let’s deconstruct the steps to understand what we've done:

  • With @JsonldResource we marked the POJO for processing as a JSON-LD resource
  • In the @JsonldNamespace we defined a shorthand for the vocabulary we want to use
  • The parameter we specified in @JsonldType will become the @type of the resource
  • We used the @JsonldLink annotation to add links to the resource. When processed, the name parameter will be used as a field name and also added as a key to the @context. href will be the field value and rel will be the mapped value in the @context
  • The field we marked with @JsonldId will become the @id of the resource
  • The parameter we specified in @JsonldProperty will become the value mapped to the field's name in the @context

Next, let's generate the JSON-LD document.

First, we should register the JsonldModule in the ObjectMapper. This module contains a custom Serializer that Jackson will use for POJOs marked with the @JsonldResource annotation.

Then, we'll continue and use the ObjectMapper to generate the JSON-LD document:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JsonldModule());

Person person = new Person("http://example.com/person/1234", "Example Name");
String personJsonLd = objectMapper.writeValueAsString(person);

As a result, the personJsonLd variable should now contain:

{
  "@type": "s:Person",
  "@context": {
    "s": "http://schema.org/",
    "name": "s:name",
    "knows": {
      "@id": "s:knows",
      "@type": "@id"
    }
  },
  "name": "Example Name",
  "@id": "http://example.com/person/1234",
  "knows": "http://example.com/person/2345"
}

4.3. Considerations

Before we choose this library for a project, we should consider the following:

  • Using the @vocab keyword is not possible, so we'll have to either use the @JsonldNamespace to provide a shorthand for resolving field names or write out the full Internationalized Resource Identifier (IRI) every time
  • We can only define links at compile-time, so in order to add a link runtime, we would need to use reflection to change that parameter in the annotation

5. Serialization With Hydra-Jsonld

Hydra-Jsonld is a module of the Hydra-Java library, which is primarily built to enable convenient JSON-LD response creation for Spring applications. It uses the Hydra Vocabulary to make the JSON-LD documents more expressive.

However, the Hydra-Jsonld module contains a Jackson Serializer and some annotations that we can use to generate JSON-LD documents outside of the Spring Framework.

5.1. Maven Dependencies

First, let's add the dependency for hydra-jsonld to the pom.xml:

<dependency>
    <groupId>de.escalon.hypermedia</groupId>
    <artifactId>hydra-jsonld</artifactId>
    <version>0.4.2</version>
</dependency>

5.2. Example

Secondly, let's annotate our POJO for @context generation.

Hydra-Jsonld automatically generates a default @context without the need for annotations. If we're satisfied with the defaults, we only need to add the @id to get a valid JSON-LD document.

The default vocabulary will be the schema.org vocabulary, the @type the Java class name, and the public properties of the POJO will all be included in the resulting JSON-LD document.

In this example, let's override these defaults with custom values:

@Vocab("http://example.com/vocab/")
@Expose("person")
public class Person {
    private String id;
    private String name;

    // constructor

    @JsonProperty("@id")
    public String getId() {
        return id;
    }

    @Expose("fullName")
    public String getName() {
        return name;
    }
}

Again, let’s take a closer look at the steps involved:

  • Compared to the Jackson-Jsonld example, we left out the knows field from our POJO because of the limitations of Hydra-Jsonld outside of the Spring Framework
  • We set our preferred vocabulary with the @Vocab annotation
  • By using the @Expose annotation on the class, we set a different resource @type
  • We used the same @Expose annotation on a property to set its mapping to a custom value in the @context
  • For generating the @id from a property, we used the @JsonProperty annotation from Jackson

Next, let's configure an instance of a Jackson Module that we can register in the ObjectMapper. We'll add the JacksonHydraSerializer as a BeanSerializerModifier so it can be applied to all POJOs that are being serialized:

SimpleModule getJacksonHydraSerializerModule() {
    return new SimpleModule() {
        @Override
        public void setupModule(SetupContext context) {
            super.setupModule(context);

            context.addBeanSerializerModifier(new BeanSerializerModifier() {
                @Override
                public JsonSerializer<?> modifySerializer(
                  SerializationConfig config, 
                  BeanDescription beanDesc, 
                  JsonSerializer<?> serializer) {
                    if (serializer instanceof BeanSerializerBase) {
                        return new JacksonHydraSerializer((BeanSerializerBase) serializer);
                    } else {
                        return serializer;
                    }
                }
            });
        }
    };
}

Then let's register the Module in ObjectMapper and use it. We should also set the ObjectMapper to only include non-null values to produce a valid JSON-LD document:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(getJacksonHydraSerializerModule());
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

Person person = new Person("http://example.com/person/1234", "Example Name");

String personJsonLd = objectMapper.writeValueAsString(person);

Now, the personJsonLd variable should contain:

{
  "@context": {
    "@vocab": "http://example.com/vocab/",
    "name": "fullName"
  },
  "@type": "person",
  "name": "Example Name",
  "@id": "http://example.com/person/1234"
}

5.3. Considerations

Although it's technically possible to use Hydra-Jsonld outside of the Spring Framework, it was originally designed for usage with Spring-HATEOAS. As a result, there's no way to generate links with annotations as we saw in Jackson-Jsonld. On the other hand, they are generated for some Spring-specific classes automatically.

Before we choose this library for a project, we should consider the following:

  • Using it with the Spring Framework will enable additional features
  • There's no easy way to generate links if we're not using the Spring Framework
  • We cannot disable the usage of @vocab, we can only override it

6. Deserialization With Jsonld-Java and Jackson

Jsonld-Java is the Java implementation of the JSON-LD 1.0 specification and API, which is unfortunately not the latest version.

For an implementation of the 1.1 specification version, have a look at the Titanium JSON-LD library.

To deserialize a JSON-LD document, let's transform it with a JSON-LD API feature, called compaction, to a format that we can map to a POJO with ObjectMapper.

6.1. Maven Dependencies

First, let's add the dependency for jsonld-java:

<dependency>
    <groupId>com.github.jsonld-java</groupId>
    <artifactId>jsonld-java</artifactId>
    <version>0.13.0</version>
</dependency>

6.2. Example

Let's work with this JSON-LD document as our input:

{
  "@context": {
    "@vocab": "http://schema.org/",
    "knows": {
      "@type": "@id"
    }
  },
  "@type": "Person",
  "@id": "http://example.com/person/1234",
  "name": "Example Name",
  "knows": "http://example.com/person/2345"
}

For the sake of simplicity, let's assume we have the content of the document in a String variable called inputJsonLd.

First, let's compact it and convert it back to a String:

Object jsonObject = JsonUtils.fromString(inputJsonLd);
Object compact = JsonLdProcessor.compact(jsonObject, new HashMap<>(), new JsonLdOptions());
String compactContent = JsonUtils.toString(compact);
  • We can parse and write the JSON-LD object with methods from the JsonUtils, which is part of the Jsonld-Java library
  • When using the compact method, as a second parameter we can use an empty Map. This way, the compaction algorithm will produce a simple JSON object where the keys are resolved to their IRI forms

The compactContent variable should contain:

{
  "@id": "http://example.com/person/1234",
  "@type": "http://schema.org/Person",
  "http://schema.org/knows": {
    "@id": "http://example.com/person/2345"
  },
  "http://schema.org/name": "Example Name"
}

Secondly, let's tailor our POJO with Jackson annotations to fit such a document structure:

@JsonIgnoreProperties(ignoreUnknown = true)
public class Person {
    @JsonProperty("@id")
    private String id;
    @JsonProperty("http://schema.org/name")
    private String name;
    @JsonProperty("http://schema.org/knows")
    private Link knows;

    // constructors, getters, setters

    public static class Link {
        @JsonProperty("@id")
        private String id;

        // constructors, getters, setters
    }
}

And finally, let's map the JSON-LD to the POJO:

ObjectMapper objectMapper = new ObjectMapper();
Person person = objectMapper.readValue(compactContent, Person.class);

7. Conclusion

In this article, we looked at two Jackson-based libraries for serializing a POJO into a JSON-LD document, and one way to deserialize a JSON-LD into a POJO.

As we've highlighted, both serialization libraries have shortcomings that we should consider before using them. If we need to use more features of JSON-LD than these libraries can offer, we could approach creating our document via an RDF library with JSON-LD output format.

As usual, the source code can be found over on GitHub.

Jackson bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Filip Kolarik
3 months ago

Hi,
please, can you help me understand why Titanium JSON-LD 1.1 processor ‘is not yet ready’? Titanium passes 99% of the official test suite. Only feature that is missing is an extraction from HTML and as far as I know it’s not supported by json-ld-java as well.

Feel free to report any issue. Your help and support is welcome.

https://w3c.github.io/json-ld-api/reports/#subj_Titanium_Java

Thank you,
Filip (I’m the author)

Loredana Crusoveanu
2 months ago
Reply to  Filip Kolarik

Hi Filip,

Thanks for the comment. I think we interpreted the remaining items in the roadmap as “not ready”. But that makes sense, we’ll update the article. I can edit this sentence now, then add an internal improvement to cover the library more, but that will take longer to complete.

Cheers.

Comments are closed on this article!