← Back to list

Simplifying Your Structured Data Model With Apache Jena

Easily Construct Object Hierarchies And Build Dynamic Forms

Liat Grinshpun in Duda · 2022-03-03 08:16 · 196 claps · 5.2 min read
#java #apache-jena #json-ld #schema-markup #software-engineering
Open on Medium ↗

Simplifying Your Structured Data Model With Apache Jena

Simplifying Your Structured Data Model By Liat

Simplifying Your Structured Data Model By Liat

The Task At Hand

Say your customer has a business that they want to be displayed on Google’s results page, like so:

Example taken from Google’s rich results documentation.

Example taken from Google’s rich results documentation.

You have the business’ name, address, website and opening hours. In order for Google and other search engines to be able to easily crawl and extract this info from your site, you can add it as structured data to your markup, without affecting the rendered page. It would look something like this:

[embed]

Should be pretty simple, right? Well, sort of…

Backtracking a bit…

If you’re unfamiliar with RDF (Resource Description Framework), it’s a way to model information by making statements about resources and the relationships between them. These statements are in the form of subject, predicate, object, also known as triples. The subject is a resource, the predicate is a property of this resource, and the object is its value.

To get a feel of what I’m talking about, let’s take a look at this graph:

In this example, we have a LocalBusiness resource (subject) with the name “My Business” (object). The name is another resource, and the relationship between the two is that “name” is a property — or predicate — of the class LocalBusiness. Same goes for the “openingHoursSpecification” property, only its value is not a literal but another structure.

There are many ways to serialize such data models, one of which is JSON-LD. This is an extension of plain-old JSON that can represent linked data (or structured data), so whoever parses your object will see that you use the vocabulary of schema.org, and can expect your LocalBusiness object to contain certain properties with certain structures.

Starting Small

In some cases, the model may be small enough that it’s feasible to represent it as data classes in your project. Then you can easily construct objects and serialize them into any RDF representation.

For our MVP we did exactly that — we started off with a single type, LocalBusiness, and chose only the properties that Google defined as required or recommended for displaying rich results. To serialize it into the JSON-LD string above we used hydra-java, as it injects the schema.org context by default, it’s built over Jackson and requires very little additional effort.

However, in other cases, this approach just won’t do. How would you handle very large models, or ones that change occasionally? What if your application receives models as input, so you can’t implement any specific class in your code?

Huge Models Create Huge Problems

Our end goal was to build a sort of dynamic form, to collect data from customers according to their chosen business type. Since schema.org defines over 100 subtypes of LocalBusiness, creating classes for each and every one of them was not an option. I had to find a way to programmatically get all the relevant types and their properties.

Luckily, schema.org has a machine-readable representation of their model in various RDF formats. Not-so-luckily, parsing it proved to be a bit challenging.

In order to have all the required info easily queryable, I wanted to turn the flat representation into a hierarchy of this sort:

{
 "typeA": {
   "parents": […],
   "children": […],
   "fields": […] 
 },
 "typeB": {…}
}

This is a compact representation of the model. Each type only holds its immediate parents, children and fields, and retrieving all the fields of a given type can be done iteratively.

So, how would you go about doing that?

Some Google magic led me to these libraries: Apache Jena and Eclipse RDF4J. Both are open-source, powerful frameworks that handle reading, creating, parsing and querying RDF.

This post focuses on the reading and parsing part, which, unfortunately, didn’t get much attention in the official docs of either library. The choice between them came down to which was more straightforward to use for this particular issue.

Using Apache Jena

Let’s start with a few basic concepts:

  • model — an RDF directed graph
  • ontology — description of all entities, properties and relationships of a certain domain
  • resource — something we want to describe in the model, in other words — a node in the graph. The resources we deal with in this post are types and properties
  • class — another word for ‘type’

To use the library in your project, first import from Gradle:

implementation "org.apache.jena:apache-jena-libs:4.0.0"

Now, let’s read our RDF input into a Jena model. The input can be a URL or file in any one of the known formats for RDF serialization. In this example, we use the URL of schema.org’s vocabulary definition in JSON-LD format.

[embed]

This creates a model that follows the RDFS syntax and parses the schema.org vocabulary into a graph.

Next, to get a specific class, we’ll query the model using the resource’s URI:

[embed]

The URI is the ID of the resource. If you look at the schema.org definition, LocalBusiness is defined like so:

"@id": "schema:LocalBusiness"

While “schema” is shorthand for the schema namespace that is defined at the top of the document:

"@context": {
    "schema": "https://schema.org/",
}

OntClass has some useful methods to get the type info we need. For example, getting the type’s subclasses and superclasses (direct or otherwise) is pretty straightforward:

[embed]This is a many-to-many relationship

However, getting the properties defined for each schema type is a slightly different story. When scanning the docs for a suitable method, you’ll see ontClass.listDeclaredProperties(true) and might assume it’ll do the trick, but quickly find out that the returned list is always empty. On the other hand, ontClass.listDeclaredProperties(false) returns every property that schema.org defines.

Apparently, all schema.org’s property resources are defined under the class Thing, which is the root type in this ontology. Instead, what defines the relationship between a property resource and a type resource is domainIncludes, which is another property.

So, to get all the actual properties of a specific type:

[embed]

Model#listResourcesWithProperty does exactly what it says on the box — given a predicate and an object, it returns all the subjects that this statement holds for them. In this case, it’s all the property resources whose “domainIncludes” is LocalBusiness.

Another relationship is rangeIncludes, which defines the structure of properties.

Using the above method will give us all the property resources, whose structure can be a specific type:

[embed]

Alternatively, to find all the possible types that a specific property can be, use the following:

[embed]

Model#listObjectsOfProperty takes a subject and a predicate, and returns all the possible objects (values) it can have.

After extracting all the needed resources, getting their names is as simple as calling res.getLocalName() .

Wrapping Up

As you can see, the final implementation is rather simple compared to researching a whole new topic and framework. To further improve this solution, it’s possible to do an on-demand validation on inputs, which I will not cover here (I suggest taking a look at [Model#listStatements](https://jena.apache.org/documentation/javadoc/jena/org/apache/jena/rdf/model/Model.html#listStatements()) to get you started). But this should give you a good enough basis to go and explore all that Jena has to offer.


메타데이터
post_id
545dd93236a8
slug
simplifying-your-structured-data-model-using-apache-jena-545dd93236a8
url
https://medium.com/duda/simplifying-your-structured-data-model-using-apache-jena-545dd93236a8
canonical_url
https://medium.com/duda/simplifying-your-structured-data-model-using-apache-jena-545dd93236a8
author_url
https://medium.com/@liatgrinshpun
status
ok
fetched_at
2026-07-21 08:50:42