Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/narazaka/json-schema-serializer
JSON Schema based serializer for ruby
https://github.com/narazaka/json-schema-serializer
json json-schema ruby serializer
Last synced: 2 months ago
JSON representation
JSON Schema based serializer for ruby
- Host: GitHub
- URL: https://github.com/narazaka/json-schema-serializer
- Owner: Narazaka
- License: zlib
- Created: 2019-11-27T08:25:24.000Z (about 5 years ago)
- Default Branch: master
- Last Pushed: 2020-12-26T15:46:21.000Z (about 4 years ago)
- Last Synced: 2024-11-21T07:50:44.663Z (3 months ago)
- Topics: json, json-schema, ruby, serializer
- Language: Ruby
- Size: 97.7 KB
- Stars: 2
- Watchers: 4
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# JSON::Schema::Serializer
[![Actions Status](https://github.com/Narazaka/json-schema-serializer/workflows/Ruby/badge.svg)](https://github.com/Narazaka/json-schema-serializer/actions)
[![Gem Version](https://badge.fury.io/rb/json-schema-serializer.svg)](https://badge.fury.io/rb/json-schema-serializer)JSON Schema based serializer
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'json-schema-serializer'
```And then execute:
$ bundle
Or install it yourself as:
$ gem install json-schema-serializer
## Usage
```ruby
require "json/schema/serializer"schema = {
type: "object",
properties: {
id: { type: "integer" },
name: { type: "string" },
fuzzy: { type: ["string", "integer", "null"] },
},
required: ["id"],
}serializer = JSON::Schema::Serializer.new(schema)
serializer.serialize({id: "42", name: "me", foo: "bar", fuzzy: "1000"})
# => {"id"=>42, "name"=>"me", "fuzzy"=>"1000"}
# "42" -> 42! type coerced!serializer.serialize({id: "42", name: "me", fuzzy: 42})
# => {"id"=>42, "name"=>"me", "fuzzy"=>42}
serializer.serialize({id: "42", name: "me"})
# => {"id"=>42, "name"=>"me", "fuzzy"=>nil}
# multiple type auto select!serializer.serialize({})
# => {"id"=>0, "name"=>nil, "fuzzy"=>nil}
# nil -> 0! required property's type coerced!serializer.serialize({id: 10, name: "I don't need null keys!"}).compact
# => {"id"=>10, "name"=>"I don't need null keys!"}
# compact it!class A
def id
42
end
end
serializer.serialize(A.new)
# => {"id"=>42, "name"=>nil, "fuzzy"=>nil}
# method also allowedclass Schema
def type
:string
end
end
serializer2 = JSON::Schema::Serializer.new(Schema.new)
serializer2.serialize(32)
# => "32"
# non-hash schema allowed#
# object injector allowed!
#class FooSerializer
def initialize(model)
@model = model
enddef first
@model.first
enddef count
@model.size
end
endserializer_injected = JSON::Schema::Serializer.new(
{
type: :object,
inject: :Foo,
properties: {
first: { type: :integer },
count: { type: :integer },
},
},
{
inject_key: :inject,
injectors: {
Foo: FooSerializer,
},
},
)serializer_injected.serialize([1, 2, 3])
# => {"first"=>1, "count"=>3}#
# object injector with context
#class BarSerializer
def initialize(model, context = nil)
@model = model
@context = context
enddef id
@model[:id]
enddef score
@context[@model[:id]]
end
endinject_context = {
1 => 100,
2 => 200,
}serializer_injected_with_context = JSON::Schema::Serializer.new(
{
type: :object,
inject: :Bar,
properties: {
id: { type: :integer },
score: { type: :integer },
},
},
{
inject_key: :inject,
injectors: {
Bar: BarSerializer,
},
inject_context: inject_context,
},
)serializer_injected_with_context.serialize({ id: 1 })
# => { "id" => 1, "score" => 100 }#
# inject in serializer
#class ParentSerializer
include JSON::Schema::Serializer::WithContextdef initialize(model, context = nil)
@model = model
@context = context
enddef id
@model[:id]
enddef score
@context[:parent_scores][@model[:id]]
enddef child
# it can be
# with_context(context) { data }
# with_context(data, context)
# with_context(data: data, context: context)
with_context(@context.merge(child_scores: { 1 => 100, 2 => 200 })) do
@model[:child]
end
end
endclass ChildSerializer
def initialize(model, context = nil)
@model = model
@context = context
enddef id
@model[:id]
enddef score
@context[:child_scores][@model[:id]]
end
endserializer_injected_with_context_in_serializer = JSON::Schema::Serializer.new(
{
type: :object,
inject: :Parent,
properties: {
id: { type: :integer },
score: { type: :integer },
child: {
type: :object,
inject: :Child,
properties: {
id: { type: :integer },
score: { type: :integer },
},
},
},
},
{
inject_key: :inject,
injectors: {
Parent: ParentSerializer,
Child: ChildSerializer,
},
inject_context: { 1 => 10, 2 => 20 },
},
)serializer_injected_with_context_in_serializer.serialize({ id: 1, child: { id: 2 } })
# => { "id" => 1, "score" => 10, "child" => { "id" => 2, "score" => 200 } }#
# also you can inject context with arraylike data
#class ItemsSerializer
include JSON::Schema::Serializer::WithContextdef initialize(models, context = nil)
@models = models
@context = context
enddef map(&block)
context = (@context || {}).merge(scores: {...})
@models.map { |model| block.call(with_context(model, context)) }
# CAUTION!
# not like below!
# with_context(@models.map(&block), context)
# with_context(context) { @models.map(&block) }
end
end#
# inject model can initialize by keywords
#class KeywordSerializer
def initialize(data:, context: nil)
@data = data
@context = context
end...
endserializer_with_keyword_init_inject = JSON::Schema::Serializer.new(
{
type: :object,
inject: :Keyword,
properties: { ... },
},
{
inject_key: :inject,
injectors: {
Keyword: KeywordSerializer,
Child: ChildSerializer,
},
inject_by_keyword: true, # <- keyword_init!
},
)
```### "additionalProperties"
"additionalProperties" is allowed but must be a schema object or `false`. (not `true`)
If "additionalProperties" does not exists, this serializer works as `{ additionalProperties": false }`.
### `$ref` resolving
`JSON::Schema::Serializer` does not resolve `$ref` so use external resolver.
with `hana` and `json_refs` gem example:
```ruby
require "hana"
require "json_refs"
require "json/schema/serializer"schema = {
"type" => "object",
"properties" => {
"foo" => { "type" => "integer" },
"bar" => { "$ref" => "#/properties/foo" },
},
}serializer = JSON::Schema::Serializer.new(JsonRefs.(schema))
serializer.serialize({foo: 0, bar: "42"})
# => {"foo"=>0, "bar"=>42}# resolver option also available
def walk(all, part)
if part.is_a?(Array)
part.map { |item| walk(all, item) }
elsif part.is_a?(Hash)
ref = part["$ref"] || part[:"$ref"]
if ref
Hana::Pointer.new(ref[1..-1]).eval(all)
else
part.map { |k, v| [k, walk(all, v)] }.to_h
end
else
part
end
endserializer2 = JSON::Schema::Serializer.new(schema["properties"]["bar"], {
resolver: ->(part_schema) do
walk(JsonRefs.(schema), part_schema))
end
})
```## JSON::Schema::Serializer API
### .new(schema, options = nil)
The initializer.
#### schema [any]
JSON schema object. The serializer tries schema["type"], schema[:type] and schema.type!
#### options [Hash]
options
#### options[:resolver] [Proc]
schema object `$ref` resolver
#### options[:schema_key_transform_for_input] [Proc]
input key transform
```ruby
new({
type: :object,
properties: {
userCount: { type: :integer },
},
}, { schema_key_transform_for_input: ->(name) { name.underscore } }).serialize({ user_count: 1 }) == { "userCount" => 1 }
```#### options[:schema_key_transform_for_output] [Proc]
output key transform
```ruby
new({
type: :object,
properties: {
userCount: { type: :integer },
},
}, { schema_key_transform_for_output: ->(name) { name.underscore } }).serialize({ userCount: 1 }) == { "user_count" => 1 }
```#### options[:injectors] [Hashlike, Class], options[:inject_key] [String, Symbol], options[:inject_context] [any], options[:inject_by_keyword] [Boolean]
If schema has inject key, the serializer treats data by `injectors[inject_key].new(data)` (or `injectors.send(inject_key).new(data)`).
And if `inject_context` is present, `injectors[inject_key].new(data, inject_context)` (or `injectors.send(inject_key).new(data, inject_context)`).
And if `inject_by_keyword` is true, `new(data, inject_context)` will be `new(data: data, context: inject_context)`.
See examples in [Usage](#usage).
CAUTION: In many case you should define the `nil?` method in the injector class because Injector always initialized by `Injector.new(obj)` even if obj == nil.
#### options[:null_through] [Boolean]
If data is null, always serialize null.
```ruby
new({ type: :string }, { null_through: true }).serialize(nil) == nil
```#### options[:empty_string_number_coerce_null] [Boolean]
If data == "" in integer or number schema, returns nil.
```ruby
new({ type: :integer }, { empty_string_number_coerce_null: true }).serialize("") == nil
```#### options[:empty_string_boolean_coerce_null] [Boolean]
If data == "" in boolean schema, returns nil.
```ruby
new({ type: :boolean }, { empty_string_boolean_coerce_null: true }).serialize("") == nil
```#### options[:false_values] [Enumerable]
If specified, boolean schema treats `!false_values.include?(data)`.
```ruby
new({ type: :boolean }, { false_values: Set.new([false]) }).serialize(nil) == true
```#### options[:no_boolean_coerce] [Boolean]
If true, boolean schema treats only `true` to be `true`.
```ruby
new({ type: :boolean }, { no_boolean_coerce: true }).serialize(1) == false
```#### options[:guard_primitive_in_structure] [Boolean]
If true, array or object schema does not accept primitive data and returns empty value.
```ruby
new({ type: :object }, { guard_primitive_in_structure: true }).serialize(1) == {}
new({ type: :object }, { guard_primitive_in_structure: true, null_through: true }).serialize(1) == nil
```### #serialize(data)
Serialize the object data by the schema.
#### data [any]
Serialize target object. The serializer tries data["foo"], data[:foo] and data.foo!
## JSON::Schema::Serializer::WithContext API
### #with_context!(data, context), #with_context!(data: data, context: context), #with_context!(context) { data }
If you use `with_context!(data, context)` as the return value of the serializer, then "child" serializer can use that context.
See examples in [Usage](#usage).
## License
Zlib License
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/Narazaka/json-schema-serializer.