This is a Scala library to work with JSON-LD. It's build on top of Circe and follows the design choices of its API.
The library comes with an API allowing creation of the following JSON-LD structures:
- values; all the factories can be found in
io.renku.jsonld.JsonLD.fromXXX
whereXXX
stands for a common value types likeInt
,Boolean
,String
, etc. plusjava.time.Instant
,java.time.LocalDate
,scala.Option
andio.renku.jsonld.EntityId
; - entities; the
io.renku.jsonld.JsonLD.entity
comes in a few variations to meet various user needs; - edges;
io.renku.jsonld.JsonLD.edge
; - arrays:
io.renku.jsonld.JsonLD.arr
; - entity IDs;
io.renku.jsonld.EntityId.of
andio.renku.jsonld.EntityId.blank
for generating blank node IDs; - named graphs;
io.renku.jsonld.NamedGraph
andio.renku.jsonld.NamedGraph.from
controlling effects happening on instantiation; - default graphs;
io.renku.jsonld.DefaultGraph
andio.renku.jsonld.DefaultGraph.from
controlling effects happening on instantiation.
Both NamedGraph
and DefaultGraph
can be instantiated with a list of entities and/or edges. In cases when they supposed to be instantiated with a generic list of JsonLD
objects, the from
factory controlling effects should be used. The factory checks if all the objects are either entities or edges and returns a failure if at least one object is of different type.
The library allows to encode any Scala object to JSON-LD. Encoders for common types like String
, Int
, Long
, java.time.Instant
and java.time.LocalDate
are provided by the library. There are also facilities for encoding
objects, arrays of objects, options, lists and sets. All the mentioned tools exists in the io.renku.jsonld.JsonLD
object.
Example:
import io.renku.jsonld._
JsonLD.fromInt(1)
JsonLD.fromOption(Some("abc"))
JsonLD.arr(JsonLD.fromString("a"), JsonLD.fromString("b"), JsonLD.fromString("c"))
JsonLD.entity(
EntityId of "http://entity/23424",
EntityTypes of (Schema.from("http://schema.org") / "Project"),
Schema.from("http://schema.org") / "name" -> JsonLD.fromString("value")
)
Improved readability can be achieved by providing encoders for the types together with implicits from
the io.renku.jsonld.syntax
package.
Example:
import io.renku.jsonld._
import io.renku.jsonld.syntax._
val schema: Schema = Schema.from("http://schema.org")
final case class MyType(name: String)
implicit val myTypeEncoder: JsonLDEncoder[MyType] = JsonLDEncoder.instance { entity =>
JsonLD.entity(
EntityId of "http://entity/23424",
EntityTypes of (schema / "Project"),
schema / "name" -> entity.name.asJsonLD
)
}
MyType(name = "some name").asJsonLD
Decoding is an operation which allows extraction of an object from JSON-LD.
Note: When you decode from flattened JSON-LD, you have to decode to a list of your object type.
import examples.ExampleSchemas.schema
import io.renku.jsonld.syntax._
import io.renku.jsonld.{EntityId, EntityTypes, JsonLD, JsonLDDecoder}
private implicit val userDecoder: JsonLDDecoder[User] = JsonLDDecoder.entity(EntityTypes.of(schema / "Person")) {
cursor =>
cursor.downField(schema / "name").as[String].map(name => User(name))
}
JsonLD
.entity(EntityId of "https://example.com/1234",
EntityTypes of schema / "Person",
schema / "name" -> "Angela".asJsonLD
)
.cursor
.as[User]
jsold4s allows defining predicates on Entity Decoders. Predicates along Entity Types work as filters in the decoding process so if there's an Entity with correct type but condition encoded in the predicate is not met on it, the entity will be skipped. An example where predicates come handy might be situation of multiple implementation of a type where each implementation differs in properties or property value.
Predicates are functions of type Cursor => JsonLDDecoder.Result[Boolean]
to give user great flexibility. An example of a predicate verifying if a schema:name
property on an Entity matches the value might be:
val predicate: Cursor => JsonLDDecoder.Result[Boolean] =
_.downField(schema / "name").as[String].map(_ == "some arbitrary name")
For more details see: src/examples/scala/examples/ConditionalDecoding.scala
Decoding big json-ld payloads into model classes may sometimes result in less satisfying performance. The library gives a tool for specifying, so called Cacheable Entities
. Entities marked as cacheable are put into internal cache when decoded for the first time. Later, when the same entity is found in other places in the model hierarchy, the decoded instance is taken from the cache rather than being decoded again. Marking an Entity as cacheable might give benefits, however, the feature has to be used wisely as adding all entities to the cache, even if they occur in the payload only once, may lead to performance which is even worse than if there's no cache at all. The reason for that is the size of the cache. If size of the cache grows, the cache simply becomes slower. So a reasonable approach is to mark cacheable only these Entities which instances either occur many times in the payload or at least twice but they are very costly to decode.
Defining a cacheable entity is simple. It requires JsonLDDecoder.cacheableEntity
instead of JsonLDDecoder.entity
. The signatures of both factories are the same.
Allows for parsing from circe-json or a string to a JsonLD object. See src/examples/scala/examples/BasicDecoding.scala
.
Flattening allows for data de-nesting in order to compress the data and have all entities in a single array. A failure
is returned if run on JSON-LD containing multiple entities with the same @id
but different content.
val nestedJson = json"""..."""
nestedJson.asJsonLD.flatten
Nested Json
{
"@id": "http://example.org/projects/46955437",
"@type": "http://schema.org/Project",
"http://schema.org/member": [
{
"@id": "http://example.org/users/82025894",
"@type": "http://schema.org/Person",
"http://schema.org/name": {
"@value": "User1"
}
}
],
"http://schema.org/name": {
"@value": "MyProject"
}
}
Flattened Json
[
{
"@id": "http://example.org/projects/46955437",
"@type": "http://schema.org/Project",
"http://schema.org/member": [
{
"@id": "http://example.org/users/82025894"
}
],
"http://schema.org/name": {
"@value": "MyProject"
}
},
{
"@id": "http://example.org/users/82025894",
"@type": "http://schema.org/Person",
"http://schema.org/name": {
"@value": "User1"
}
}
]
The following types are supported for both encoding and decoding:
- String
- Int
- Long
- Instant
- LocalDate
- EntityId
- JsonLD
- Boolean
- List
- Set
- Seq
- Option
Every JsonLD object can be turn to Json using it's toJson
method.
For More examples, please see src/examples/scala/examples
JsonLD4s library allows defining and generating ontologies.
Defining ontology of a specific type should be done using the io.renku.jsonld.ontology.Type
class. An example definition can look like follows:
import io.renku.jsonld.ontology._
import io.renku.jsonld.Schema
val prov: Schema = Schema.from("http://www.w3.org/ns/prov", separator = "#")
val renku: Schema = Schema.from("https://swissdatasciencecenter.github.io/renku-ontology", separator = "#")
val schema: Schema = Schema.from("http://schema.org")
val subtypeOntology: Type = Type.Def(
Class(schema / "Thing"),
DataProperty(schema / "name", xsd / "string")
)
val rootOntology: Type = Type.Def(
Class(prov / "Activity"),
ObjectProperties(
ObjectProperty(renku / "parameter", subtypeOntology)
),
DataProperties(DataProperty(prov / "startedAtTime", xsd / "dateTime"),
DataProperty(prov / "endedAtTime", xsd / "dateTime")
)
)
Type's class needs to be defined with the Class
type, properties linking other types with a collection of ObjectProperty
objects and simple value properties with a collection of DataProperty
objects. The library calculates properties' ranges and domains automatically during ontology generation.
Generating ontology is a trivial task which can be done using the io.renku.jsonld.ontology.generateOntology
method. The method takes a Type
definition and a Schema
.
import io.renku.jsonld.ontology._
import io.renku.jsonld.Schema
val renku: Schema = Schema.from("https://swissdatasciencecenter.github.io/renku-ontology", separator = "#")
val schema: Schema = Schema.from("http://schema.org")
val ontology: Type = Type.Def(
Class(schema / "Thing"),
DataProperty(schema / "name", xsd / "string")
)
generateOntology(ontology, renku)