diff --git a/README.md b/README.md
index 57fca0f5..cef60d94 100755
--- a/README.md
+++ b/README.md
@@ -32,10 +32,10 @@ This gem also implements an optimized streaming writer used for generating JSON-
* Each statement written as a separate node in expanded/flattened form.
* `RDF List`s are written as separate nodes using `rdf:first` and `rdf:rest` properties.
-The order of triples retrieved from the `RDF::Enumerable` dataset determines the way that JSON-LD node objects are written; for best results, statements should be ordered by _graph name_, _subect_, _predicate_ and _object_.
+The order of triples retrieved from the `RDF::Enumerable` dataset determines the way that JSON-LD node objects are written; for best results, statements should be ordered by _graph name_, _subject_, _predicate_ and _object_.
### MultiJson parser
-The [MultiJson](https://rubygems.org/gems/multi_json) gem is used for parsing JSON; this defaults to the native JSON parser, but will use a more performant parser if one is available. A specific parser can be specified by adding the `:adapter` option to any API call. See [MultiJson](https://rubygems.org/gems/multi_json) for more information.
+The [MultiJson](https://rubygems.org/gems/multi_json) gem is used for parsing and serializing JSON; this defaults to the native JSON parser/serializer, but will use a more performant parser if one is available. A specific parser can be specified by adding the `:adapter` option to any API call. Additionally, a custom serialilzer may be specified by passing the `:serializer` option to {JSON::LD::Writer} or methods of {JSON::LD::API}. See [MultiJson](https://rubygems.org/gems/multi_json) for more information.
### JSON-LD-star (RDFStar)
@@ -624,8 +624,7 @@ To get a local working copy of the development repository, do:
which you will be asked to agree to on the first commit to a repo within the organization.
Note that the agreement applies to all repos in the [Ruby RDF](https://github.com/ruby-rdf/) organization.
-License
--------
+## License
This is free and unencumbered public domain software. For more information,
see or the accompanying {file:UNLICENSE} file.
@@ -641,7 +640,7 @@ see or the accompanying {file:UNLICENSE} file.
[Backports]: https://rubygems.org/gems/backports
[JSON-LD]: https://www.w3.org/TR/json-ld11/ "JSON-LD 1.1"
[JSON-LD API]: https://www.w3.org/TR/json-ld11-api/ "JSON-LD 1.1 Processing Algorithms and API"
-[JSON-LD Framing]: https://www.w3.org/TR/json-ld11-framing/ "JSON-LD Framing 1.1"
+[JSON-LD Framing]: https://www.w3.org/TR/json-ld11-framing/ "JSON-LD 1.1 Framing"
[Promises]: https://dom.spec.whatwg.org/#promises
[jsonlint]: https://rubygems.org/gems/jsonlint
[Sinatra]: https://www.sinatrarb.com/
diff --git a/VERSION b/VERSION
index e4604e3a..be94e6f5 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.2.1
+3.2.2
diff --git a/lib/json/ld.rb b/lib/json/ld.rb
index ef7ab5ad..3b7caf00 100644
--- a/lib/json/ld.rb
+++ b/lib/json/ld.rb
@@ -46,6 +46,9 @@ module LD
# Default context when compacting without one being specified
DEFAULT_CONTEXT = "http://schema.org"
+ # Acceptable MultiJson adapters
+ MUTLI_JSON_ADAPTERS = %i(oj json_gem json_pure ok_json yajl nsjsonseerialization)
+
KEYWORDS = Set.new(%w(
@annotation
@base
diff --git a/lib/json/ld/api.rb b/lib/json/ld/api.rb
index cf20320b..af0266e4 100644
--- a/lib/json/ld/api.rb
+++ b/lib/json/ld/api.rb
@@ -126,7 +126,8 @@ def initialize(input, context, **options, &block)
case remote_doc.document
when String
- MultiJson.load(remote_doc.document, **options)
+ mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
+ MultiJson.load(remote_doc.document, **mj_opts)
else
# Already parsed
remote_doc.document
@@ -155,6 +156,9 @@ def initialize(input, context, **options, &block)
#
# @param [String, #read, Hash, Array] input
# The JSON-LD object to copy and perform the expansion upon.
+ # @param [Proc] serializer (nil)
+ # A Serializer method used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
+ # See {JSON::LD::API.serializer}.
# @param [Hash{Symbol => Object}] options
# @option options (see #initialize)
# @raise [JsonLdError]
@@ -167,7 +171,7 @@ def initialize(input, context, **options, &block)
# @return [Object, Array]
# If a block is given, the result of evaluating the block is returned, otherwise, the expanded JSON-LD document
# @see https://www.w3.org/TR/json-ld11-api/#expansion-algorithm
- def self.expand(input, framing: false, **options, &block)
+ def self.expand(input, framing: false, serializer: nil, **options, &block)
result = doc_base = nil
API.new(input, options[:expandContext], **options) do
result = self.expand(self.value, nil, self.context,
@@ -180,6 +184,7 @@ def self.expand(input, framing: false, **options, &block)
# Finally, if element is a JSON object, it is wrapped into an array.
result = [result].compact unless result.is_a?(Array)
+ result = serializer.call(result, **options) if serializer
if block_given?
case block.arity
@@ -204,6 +209,9 @@ def self.expand(input, framing: false, **options, &block)
# The JSON-LD object to copy and perform the compaction upon.
# @param [String, #read, Hash, Array, JSON::LD::Context] context
# The base context to use when compacting the input.
+ # @param [Proc] serializer (nil)
+ # A Serializer instance used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
+ # See {JSON::LD::API.serializer}.
# @param [Boolean] expanded (false) Input is already expanded
# @param [Hash{Symbol => Object}] options
# @option options (see #initialize)
@@ -215,7 +223,7 @@ def self.expand(input, framing: false, **options, &block)
# If a block is given, the result of evaluating the block is returned, otherwise, the compacted JSON-LD document
# @raise [JsonLdError]
# @see https://www.w3.org/TR/json-ld11-api/#compaction-algorithm
- def self.compact(input, context, expanded: false, **options)
+ def self.compact(input, context, expanded: false, serializer: nil, **options)
result = nil
options = {compactToRelative: true}.merge(options)
@@ -238,6 +246,7 @@ def self.compact(input, context, expanded: false, **options)
end
result = ctx.merge(result) unless ctx.fetch('@context', {}).empty?
end
+ result = serializer.call(result, **options) if serializer
block_given? ? yield(result) : result
end
@@ -251,6 +260,9 @@ def self.compact(input, context, expanded: false, **options)
# @param [String, #read, Hash, Array, JSON::LD::EvaluationContext] context
# An optional external context to use additionally to the context embedded in input when expanding the input.
# @param [Boolean] expanded (false) Input is already expanded
+ # @param [Proc] serializer (nil)
+ # A Serializer instance used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
+ # See {JSON::LD::API.serializer}.
# @param [Hash{Symbol => Object}] options
# @option options (see #initialize)
# @option options [Boolean] :createAnnotations
@@ -262,7 +274,7 @@ def self.compact(input, context, expanded: false, **options)
# @return [Object, Hash]
# If a block is given, the result of evaluating the block is returned, otherwise, the flattened JSON-LD document
# @see https://www.w3.org/TR/json-ld11-api/#framing-algorithm
- def self.flatten(input, context, expanded: false, **options)
+ def self.flatten(input, context, expanded: false, serializer: nil, **options)
flattened = []
options = {
compactToRelative: true,
@@ -318,6 +330,7 @@ def self.flatten(input, context, expanded: false, **options)
end
end
+ flattened = serializer.call(flattened, **options) if serializer
block_given? ? yield(flattened) : flattened
end
@@ -350,7 +363,7 @@ def self.flatten(input, context, expanded: false, **options)
# If a block is given, the result of evaluating the block is returned, otherwise, the framed JSON-LD document
# @raise [InvalidFrame]
# @see https://www.w3.org/TR/json-ld11-api/#framing-algorithm
- def self.frame(input, frame, expanded: false, **options)
+ def self.frame(input, frame, expanded: false, serializer: nil, **options)
result = nil
options = {
base: (RDF::URI(input) if input.is_a?(String)),
@@ -379,7 +392,8 @@ def self.frame(input, frame, expanded: false, **options)
requestProfile: 'http://www.w3.org/ns/json-ld#frame',
**options)
if remote_doc.document.is_a?(String)
- MultiJson.load(remote_doc.document)
+ mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
+ MultiJson.load(remote_doc.document, **mj_opts)
else
remote_doc.document
end
@@ -467,6 +481,7 @@ def self.frame(input, frame, expanded: false, **options)
result
end
+ result = serializer.call(result, **options) if serializer
block_given? ? yield(result) : result
end
@@ -528,18 +543,21 @@ def self.toRdf(input, expanded: false, **options, &block)
# The resulting `Array` is either returned or yielded, if a block is given.
#
# @param [RDF::Enumerable] input
+ # @param [Boolean] useRdfType (false)
+ # If set to `true`, the JSON-LD processor will treat `rdf:type` like a normal property instead of using `@type`.
+ # @param [Boolean] useNativeTypes (false) use native representations
+ # @param [Proc] serializer (nil)
+ # A Serializer instance used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
+ # See {JSON::LD::API.serializer}.
# @param [Hash{Symbol => Object}] options
# @option options (see #initialize)
- # @option options [Boolean] :useRdfType (false)
- # If set to `true`, the JSON-LD processor will treat `rdf:type` like a normal property instead of using `@type`.
- # @option options [Boolean] :useNativeTypes (false) use native representations
# @yield jsonld
# @yieldparam [Hash] jsonld
# The JSON-LD document in expanded form
# @yieldreturn [Object] returned object
# @return [Object, Hash]
# If a block is given, the result of evaluating the block is returned, otherwise, the expanded JSON-LD document
- def self.fromRdf(input, useRdfType: false, useNativeTypes: false, **options, &block)
+ def self.fromRdf(input, useRdfType: false, useNativeTypes: false, serializer: nil, **options, &block)
result = nil
API.new(nil, nil, **options) do
@@ -548,6 +566,7 @@ def self.fromRdf(input, useRdfType: false, useNativeTypes: false, **options, &bl
useNativeTypes: useNativeTypes)
end
+ result = serializer.call(result, **options) if serializer
block_given? ? yield(result) : result
end
@@ -648,7 +667,8 @@ def self.loadRemoteDocument(url,
end
else
validate_input(remote_doc.document, url: remote_doc.documentUrl) if validate
- MultiJson.load(remote_doc.document, **options)
+ mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
+ MultiJson.load(remote_doc.document, **mj_opts)
end
end
@@ -682,8 +702,8 @@ def self.documentLoader(url, extractAllScripts: false, profile: nil, requestProf
base_uri ||= url.base_uri if url.respond_to?(:base_uri)
content_type = options[:content_type]
content_type ||= url.content_type if url.respond_to?(:content_type)
- context_url = if url.respond_to?(:links) && url.links
- (content_type == 'appliaction/json' || content_type.match?(%r(application/(^ld)+json)))
+ context_url = if url.respond_to?(:links) && url.links &&
+ (content_type == 'application/json' || content_type.match?(%r(application/(^ld)+json)))
link = url.links.find_link(LINK_REL_CONTEXT)
link.href if link
end
@@ -759,7 +779,8 @@ def self.load_html(input, url:,
raise JSON::LD::JsonLdError::LoadingDocumentFailed, "Script tag has type=#{element.attributes['type']}" unless element.attributes['type'].to_s.start_with?('application/ld+json')
content = element.inner_html
validate_input(content, url: url) if options[:validate]
- MultiJson.load(content, **options)
+ mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
+ MultiJson.load(content, **mj_opts)
elsif extractAllScripts
res = []
elements = if profile
@@ -773,7 +794,8 @@ def self.load_html(input, url:,
elements.each do |element|
content = element.inner_html
validate_input(content, url: url) if options[:validate]
- r = MultiJson.load(content, **options)
+ mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
+ r = MultiJson.load(content, **mj_opts)
if r.is_a?(Hash)
res << r
elsif r.is_a?(Array)
@@ -788,12 +810,27 @@ def self.load_html(input, url:,
raise JSON::LD::JsonLdError::LoadingDocumentFailed, "No script tag found" unless element
content = element.inner_html
validate_input(content, url: url) if options[:validate]
- MultiJson.load(content, **options)
+ mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
+ MultiJson.load(content, **mj_opts)
end
rescue MultiJson::ParseError => e
raise JSON::LD::JsonLdError::InvalidScriptElement, e.message
end
+ ##
+ # The default serializer for serialzing Ruby Objects to JSON.
+ #
+ # Defaults to `MultiJson.dump`
+ #
+ # @param [Object] object
+ # @param [Array