Add :context option on JSON.LD.Encoder

This commit is contained in:
Marcel Otto 2022-04-21 01:42:10 +02:00
parent d6cda29cea
commit 851a1b0c19
6 changed files with 186 additions and 14 deletions

View File

@ -5,6 +5,17 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
[Keep a CHANGELOG](http://keepachangelog.com).
## Unreleased
### Added
- the `JSON.LD.Encoder` now supports implicit compaction by providing a context
as a map or a URL string for a remote context with the new `:context` option
[Compare v0.3.4...HEAD](https://github.com/rdf-elixir/jsonld-ex/compare/v0.3.4...HEAD)
## 0.3.4 - 2021-12-13
Elixir versions < 1.10 are no longer supported

View File

@ -1 +1 @@
0.3.4
0.3.5-pre

View File

@ -25,9 +25,11 @@ defmodule JSON.LD.Compaction do
result
end
if Context.empty?(active_context),
do: result,
else: Map.put(result, "@context", context["@context"] || context)
cond do
Context.empty?(active_context) -> result
is_binary(context) -> Map.put(result, "@context", context)
true -> Map.put(result, "@context", context["@context"] || context)
end
end
@spec do_compact(any, Context.t(), map, String.t() | nil, boolean) :: any

View File

@ -1,5 +1,10 @@
defmodule JSON.LD.Decoder do
@moduledoc """
A decoder for JSON-LD serializations to `RDF.Dataset`s.
As for all decoders of `RDF.Serialization.Format`s, you normally won't use these
functions directly, but via one of the `read_` functions on the `JSON.LD` format
module or the generic `RDF.Serialization` module.
"""
use RDF.Serialization.Decoder

View File

@ -1,10 +1,34 @@
defmodule JSON.LD.Encoder do
@moduledoc """
An encoder for JSON-LD serializations of RDF.ex data structures.
As for all encoders of `RDF.Serialization.Format`s, you normally won't use these
functions directly, but via one of the `write_` functions on the `JSON.LD`
format module or the generic `RDF.Serialization` module.
## Options
- `:context`: When a context map or remote context URL string is given,
compaction is performed using this context
- `:base`: : Allows to specify a base URI to be used during compaction
(only when `:context` is provided).
- `:use_native_types`: If this flag is set to `true`, RDF literals with a datatype IRI
that equals `xsd:integer` or `xsd:double` are converted to a JSON numbers and
RDF literals with a datatype IRI that equals `xsd:boolean` are converted to `true`
or `false` based on their lexical form. (default: `false`)
- `:use_rdf_type`: Unless this flag is set to `true`, `rdf:type` predicates will be
serialized as `@type` as long as the associated object is either an IRI or blank
node identifier. (default: `false`)
The given options are also passed through to `Jason.encode/2`, so you can also
provide any the options this function supports, most notably the `:pretty` option.
"""
use RDF.Serialization.Encoder
alias JSON.LD.Options
alias JSON.LD.{Compaction, Options}
alias RDF.{
BlankNode,
@ -30,7 +54,8 @@ defmodule JSON.LD.Encoder do
@impl RDF.Serialization.Encoder
@spec encode(RDF.Data.t(), Options.t() | Enum.t()) :: {:ok, String.t()} | {:error, any}
def encode(data, opts \\ []) do
with {:ok, json_ld_object} <- from_rdf(data, opts) do
with {:ok, json_ld_object} <- from_rdf(data, opts),
{:ok, json_ld_object} <- maybe_compact(json_ld_object, opts) do
encode_json(json_ld_object, opts)
end
end
@ -40,9 +65,18 @@ defmodule JSON.LD.Encoder do
@spec encode!(RDF.Data.t(), Options.t() | Enum.t()) :: String.t()
@dialyzer {:nowarn_function, encode!: 1}
def encode!(data, opts \\ []) do
data
|> from_rdf!(opts)
|> encode_json!(opts)
case encode(data, opts) do
{:ok, result} -> result
{:error, error} -> raise error
end
end
defp maybe_compact(json_ld_object, opts) do
if context = Keyword.get(opts, :context) do
{:ok, Compaction.compact(json_ld_object, context, opts)}
else
{:ok, json_ld_object}
end
end
@spec from_rdf(RDF.Data.t(), Options.t() | Enum.t()) :: {:ok, [map]} | {:error, any}
@ -388,9 +422,4 @@ defmodule JSON.LD.Encoder do
defp encode_json(value, opts) do
Jason.encode(value, opts)
end
@spec encode_json!(any, [Jason.encode_opt()]) :: String.t()
defp encode_json!(value, opts) do
Jason.encode!(value, opts)
end
end

View File

@ -572,6 +572,131 @@ defmodule JSON.LD.EncoderTest do
end)
end
describe "encode options" do
test ":context with a context map" do
graph =
~I<http://manu.sporny.org/about#manu>
|> S.givenName("Manu")
|> S.familyName("Sporny")
|> S.url(~I<http://manu.sporny.org/>)
|> Graph.new()
context = %{
"givenName" => "http://schema.org/givenName",
"familyName" => "http://schema.org/familyName",
"homepage" => %{
"@id" => "http://schema.org/url",
"@type" => "@id"
}
}
assert JSON.LD.Encoder.encode!(graph, context: context, pretty: true) ==
"""
{
"@context": {
"familyName": "http://schema.org/familyName",
"givenName": "http://schema.org/givenName",
"homepage": {
"@id": "http://schema.org/url",
"@type": "@id"
}
},
"@id": "http://manu.sporny.org/about#manu",
"familyName": "Sporny",
"givenName": "Manu",
"homepage": "http://manu.sporny.org/"
}
"""
|> String.trim()
end
test ":context with a remote context" do
bypass = Bypass.open()
Bypass.expect(bypass, fn conn ->
assert "GET" == conn.method
assert "/test-context" == conn.request_path
context = %{
"@context" => %{
"givenName" => "http://schema.org/givenName",
"familyName" => "http://schema.org/familyName",
"homepage" => %{
"@id" => "http://schema.org/url",
"@type" => "@id"
}
}
}
Plug.Conn.resp(conn, 200, Jason.encode!(context))
end)
remote_context = "http://localhost:#{bypass.port}/test-context"
graph =
~I<http://manu.sporny.org/about#manu>
|> S.givenName("Manu")
|> S.familyName("Sporny")
|> S.url(~I<http://manu.sporny.org/>)
|> Graph.new()
assert JSON.LD.Encoder.encode!(graph, context: remote_context, pretty: true) ==
"""
{
"@context": "#{remote_context}",
"@id": "http://manu.sporny.org/about#manu",
"familyName": "Sporny",
"givenName": "Manu",
"homepage": "http://manu.sporny.org/"
}
"""
|> String.trim()
end
test "compaction options" do
graph =
~I<http://manu.sporny.org/about#manu>
|> S.givenName("Manu")
|> S.familyName("Sporny")
|> RDF.type(S.Person)
|> EX.foo(3.14)
|> EX.bar(EX.Bar)
|> Graph.new()
context = %{
"givenName" => "http://schema.org/givenName",
"familyName" => "http://schema.org/familyName"
}
assert JSON.LD.Encoder.encode!(graph,
context: context,
base: EX.__base_iri__(),
use_native_types: true,
use_rdf_type: true,
pretty: true
) ==
"""
{
"@context": {
"familyName": "http://schema.org/familyName",
"givenName": "http://schema.org/givenName"
},
"@id": "http://manu.sporny.org/about#manu",
"familyName": "Sporny",
"givenName": "Manu",
"http://example.com/bar": {
"@id": "Bar"
},
"http://example.com/foo": 3.14,
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type": {
"@id": "http://schema.org/Person"
}
}
"""
|> String.trim()
end
end
describe "problems" do
%{
"xsd:boolean as value" => {