json-schema-serializer 1.4.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b4c5d9d360e8b71d59ffaf2dbaef3a19cb782acdbb4c97548188f4c3339d654
4
- data.tar.gz: 180e413608d626170441274fa43c82a20cf62d78a5509cc1cee6b8ade65dbeae
3
+ metadata.gz: 8520da63406ec92ce27f94214e6377eb0235fa22745c1dd9473fb597bfc2acdd
4
+ data.tar.gz: 36de8e40ad358955d937e2b46141c0da13e9b7cff328a26194e6e52991c1b2cc
5
5
  SHA512:
6
- metadata.gz: 6b5e545a90241344d73da7d4a015ba83e0d96ec71df27f98652f755018c2c241b612cdbbcbdcf0b3fc5b10a447e493047a035cdf93c770f42214a41e90935b7b
7
- data.tar.gz: 8c875c4c2681df2ef05ae8bde119c6cd1a9cc6621e667616e8df935acfe34c2a4c459cf648730aa9e9439240d805382ff9d8033f2edecd804e8a697def9877e5
6
+ metadata.gz: a6a9d1927d21adc4c7a499313b195e63465302018e4575a83c9095236c31df86eb1037e5d06618ad07fa0674c620c5cd2b58aad0aae2ae04884f3c0f9b6592c8
7
+ data.tar.gz: f3d64406270a9b00f816106611cf1327ed5eab8bd9ec5472bb577fbfda8f0ef415404563fda4fd50008441388d03f655d82c0085f9dd8b5aba95438c1d50d9f7
@@ -1,5 +1,18 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.7.0
4
+
5
+ - add: `with_context!` api
6
+ - add: `:inject_by_keyword` option
7
+
8
+ ## 1.6.0
9
+
10
+ - add: inject context
11
+
12
+ ## 1.5.0
13
+
14
+ - add: many options
15
+
3
16
  ## 1.4.0
4
17
 
5
18
  - add: "additionalProperties" support
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Json::Schema::Serializer
1
+ # JSON::Schema::Serializer
2
2
 
3
3
  [![Actions Status](https://github.com/Narazaka/json-schema-serializer/workflows/Ruby/badge.svg)](https://github.com/Narazaka/json-schema-serializer/actions)
4
4
  [![Gem Version](https://badge.fury.io/rb/json-schema-serializer.svg)](https://badge.fury.io/rb/json-schema-serializer)
@@ -112,11 +112,183 @@ serializer_injected = JSON::Schema::Serializer.new(
112
112
 
113
113
  serializer_injected.serialize([1, 2, 3])
114
114
  # => {"first"=>1, "count"=>3}
115
+
116
+ #
117
+ # object injector with context
118
+ #
119
+
120
+ class BarSerializer
121
+ def initialize(model, context = nil)
122
+ @model = model
123
+ @context = context
124
+ end
125
+
126
+ def id
127
+ @model[:id]
128
+ end
129
+
130
+ def score
131
+ @context[@model[:id]]
132
+ end
133
+ end
134
+
135
+ inject_context = {
136
+ 1 => 100,
137
+ 2 => 200,
138
+ }
139
+
140
+ serializer_injected_with_context = JSON::Schema::Serializer.new(
141
+ {
142
+ type: :object,
143
+ inject: :Bar,
144
+ properties: {
145
+ id: { type: :integer },
146
+ score: { type: :integer },
147
+ },
148
+ },
149
+ {
150
+ inject_key: :inject,
151
+ injectors: {
152
+ Bar: BarSerializer,
153
+ },
154
+ inject_context: inject_context,
155
+ },
156
+ )
157
+
158
+ serializer_injected_with_context.serialize({ id: 1 })
159
+ # => { "id" => 1, "score" => 100 }
160
+
161
+ #
162
+ # inject in serializer
163
+ #
164
+
165
+ class ParentSerializer
166
+ include JSON::Schema::Serializer::WithContext
167
+
168
+ def initialize(model, context = nil)
169
+ @model = model
170
+ @context = context
171
+ end
172
+
173
+ def id
174
+ @model[:id]
175
+ end
176
+
177
+ def score
178
+ @context[:parent_scores][@model[:id]]
179
+ end
180
+
181
+ def child
182
+ # it can be
183
+ # with_context(context) { data }
184
+ # with_context(data, context)
185
+ # with_context(data: data, context: context)
186
+ with_context(@context.merge(child_scores: { 1 => 100, 2 => 200 })) do
187
+ @model[:child]
188
+ end
189
+ end
190
+ end
191
+
192
+ class ChildSerializer
193
+ def initialize(model, context = nil)
194
+ @model = model
195
+ @context = context
196
+ end
197
+
198
+ def id
199
+ @model[:id]
200
+ end
201
+
202
+ def score
203
+ @context[:child_scores][@model[:id]]
204
+ end
205
+ end
206
+
207
+ serializer_injected_with_context_in_serializer = JSON::Schema::Serializer.new(
208
+ {
209
+ type: :object,
210
+ inject: :Parent,
211
+ properties: {
212
+ id: { type: :integer },
213
+ score: { type: :integer },
214
+ child: {
215
+ type: :object,
216
+ inject: :Child,
217
+ properties: {
218
+ id: { type: :integer },
219
+ score: { type: :integer },
220
+ },
221
+ },
222
+ },
223
+ },
224
+ {
225
+ inject_key: :inject,
226
+ injectors: {
227
+ Parent: ParentSerializer,
228
+ Child: ChildSerializer,
229
+ },
230
+ inject_context: { 1 => 10, 2 => 20 },
231
+ },
232
+ )
233
+
234
+ serializer_injected_with_context_in_serializer.serialize({ id: 1, child: { id: 2 } })
235
+ # => { "id" => 1, "score" => 10, "child" => { "id" => 2, "score" => 200 } }
236
+
237
+ #
238
+ # also you can inject context with arraylike data
239
+ #
240
+
241
+ class ItemsSerializer
242
+ include JSON::Schema::Serializer::WithContext
243
+
244
+ def initialize(models, context = nil)
245
+ @models = models
246
+ @context = context
247
+ end
248
+
249
+ def map(&block)
250
+ context = (@context || {}).merge(scores: {...})
251
+ @models.map { |model| block.call(with_context(model, context)) }
252
+ # CAUTION!
253
+ # not like below!
254
+ # with_context(@models.map(&block), context)
255
+ # with_context(context) { @models.map(&block) }
256
+ end
257
+ end
258
+
259
+ #
260
+ # inject model can initialize by keywords
261
+ #
262
+
263
+ class KeywordSerializer
264
+ def initialize(data:, context: nil)
265
+ @data = data
266
+ @context = context
267
+ end
268
+
269
+ ...
270
+ end
271
+
272
+ serializer_with_keyword_init_inject = JSON::Schema::Serializer.new(
273
+ {
274
+ type: :object,
275
+ inject: :Keyword,
276
+ properties: { ... },
277
+ },
278
+ {
279
+ inject_key: :inject,
280
+ injectors: {
281
+ Keyword: KeywordSerializer,
282
+ Child: ChildSerializer,
283
+ },
284
+ inject_by_keyword: true, # <- keyword_init!
285
+ },
286
+ )
115
287
  ```
116
288
 
117
289
  ### "additionalProperties"
118
290
 
119
- "additionalProperties" is allowed but must be a schema object. (not boolean)
291
+ "additionalProperties" is allowed but must be a schema object or `false`. (not `true`)
120
292
 
121
293
  If "additionalProperties" does not exists, this serializer works as `{ additionalProperties": false }`.
122
294
 
@@ -167,6 +339,128 @@ serializer2 = JSON::Schema::Serializer.new(schema["properties"]["bar"], {
167
339
  })
168
340
  ```
169
341
 
342
+ ## JSON::Schema::Serializer API
343
+
344
+ ### .new(schema, options = nil)
345
+
346
+ The initializer.
347
+
348
+ #### schema [any]
349
+
350
+ JSON schema object. The serializer tries schema["type"], schema[:type] and schema.type!
351
+
352
+ #### options [Hash]
353
+
354
+ options
355
+
356
+ #### options[:resolver] [Proc]
357
+
358
+ schema object `$ref` resolver
359
+
360
+ #### options[:schema_key_transform_for_input] [Proc]
361
+
362
+ input key transform
363
+
364
+ ```ruby
365
+ new({
366
+ type: :object,
367
+ properties: {
368
+ userCount: { type: :integer },
369
+ },
370
+ }, { schema_key_transform_for_input: ->(name) { name.underscore } }).serialize({ user_count: 1 }) == { "userCount" => 1 }
371
+ ```
372
+
373
+ #### options[:schema_key_transform_for_output] [Proc]
374
+
375
+ output key transform
376
+
377
+ ```ruby
378
+ new({
379
+ type: :object,
380
+ properties: {
381
+ userCount: { type: :integer },
382
+ },
383
+ }, { schema_key_transform_for_output: ->(name) { name.underscore } }).serialize({ userCount: 1 }) == { "user_count" => 1 }
384
+ ```
385
+
386
+ #### options[:injectors] [Hashlike<String, Class>, Class], options[:inject_key] [String, Symbol], options[:inject_context] [any], options[:inject_by_keyword] [Boolean]
387
+
388
+ If schema has inject key, the serializer treats data by `injectors[inject_key].new(data)` (or `injectors.send(inject_key).new(data)`).
389
+
390
+ And if `inject_context` is present, `injectors[inject_key].new(data, inject_context)` (or `injectors.send(inject_key).new(data, inject_context)`).
391
+
392
+ And if `inject_by_keyword` is true, `new(data, inject_context)` will be `new(data: data, context: inject_context)`.
393
+
394
+ See examples in [Usage](#usage).
395
+
396
+ 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.
397
+
398
+ #### options[:null_through] [Boolean]
399
+
400
+ If data is null, always serialize null.
401
+
402
+ ```ruby
403
+ new({ type: :string }, { null_through: true }).serialize(nil) == nil
404
+ ```
405
+
406
+ #### options[:empty_string_number_coerce_null] [Boolean]
407
+
408
+ If data == "" in integer or number schema, returns nil.
409
+
410
+ ```ruby
411
+ new({ type: :integer }, { empty_string_number_coerce_null: true }).serialize("") == nil
412
+ ```
413
+
414
+ #### options[:empty_string_boolean_coerce_null] [Boolean]
415
+
416
+ If data == "" in boolean schema, returns nil.
417
+
418
+ ```ruby
419
+ new({ type: :boolean }, { empty_string_boolean_coerce_null: true }).serialize("") == nil
420
+ ```
421
+
422
+ #### options[:false_values] [Enumerable]
423
+
424
+ If specified, boolean schema treats `!false_values.include?(data)`.
425
+
426
+ ```ruby
427
+ new({ type: :boolean }, { false_values: Set.new([false]) }).serialize(nil) == true
428
+ ```
429
+
430
+ #### options[:no_boolean_coerce] [Boolean]
431
+
432
+ If true, boolean schema treats only `true` to be `true`.
433
+
434
+ ```ruby
435
+ new({ type: :boolean }, { no_boolean_coerce: true }).serialize(1) == false
436
+ ```
437
+
438
+ #### options[:guard_primitive_in_structure] [Boolean]
439
+
440
+ If true, array or object schema does not accept primitive data and returns empty value.
441
+
442
+
443
+ ```ruby
444
+ new({ type: :object }, { guard_primitive_in_structure: true }).serialize(1) == {}
445
+ new({ type: :object }, { guard_primitive_in_structure: true, null_through: true }).serialize(1) == nil
446
+ ```
447
+
448
+ ### #serialize(data)
449
+
450
+ Serialize the object data by the schema.
451
+
452
+ #### data [any]
453
+
454
+ Serialize target object. The serializer tries data["foo"], data[:foo] and data.foo!
455
+
456
+ ## JSON::Schema::Serializer::WithContext API
457
+
458
+ ### #with_context!(data, context), #with_context!(data: data, context: context), #with_context!(context) { data }
459
+
460
+ If you use `with_context!(data, context)` as the return value of the serializer, then "child" serializer can use that context.
461
+
462
+ See examples in [Usage](#usage).
463
+
170
464
  ## License
171
465
 
172
466
  Zlib License
@@ -4,7 +4,7 @@ require "json/schema/serializer/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "json-schema-serializer"
7
- spec.version = Json::Schema::Serializer::VERSION
7
+ spec.version = JSON::Schema::Serializer::VERSION
8
8
  spec.authors = %w[Narazaka]
9
9
  spec.email = %w[info@narazaka.net]
10
10
  spec.licenses = %w[Zlib]
@@ -28,9 +28,10 @@ Gem::Specification.new do |spec|
28
28
  spec.require_paths = %w[lib]
29
29
 
30
30
  spec.add_development_dependency "bundler", "~> 2.0"
31
- spec.add_development_dependency "rake", "~> 10.5"
31
+ spec.add_development_dependency "rake", "~> 13.0"
32
32
  spec.add_development_dependency "rspec", "~> 3.9"
33
33
  spec.add_development_dependency "rspec-power_assert", "~> 1.1"
34
+ spec.add_development_dependency "simplecov", ">= 0.18"
34
35
  spec.add_development_dependency "rubocop", "~> 0.76"
35
36
  spec.add_development_dependency "rubocop-airbnb", "~> 3"
36
37
  spec.add_development_dependency "prettier", ">= 0.16"
@@ -5,13 +5,29 @@ require "set"
5
5
  module JSON
6
6
  class Schema
7
7
  class Serializer
8
- def initialize(obj, options = {})
9
- @schema = options && options[:resolver] ? options[:resolver].call(obj) : obj
10
- @options = options
8
+ def initialize(schema, options = nil) # rubocop:disable Airbnb/OptArgParameters
9
+ @schema = options && options[:resolver] ? options[:resolver].call(schema) : schema
10
+ @options = options || {}
11
11
  end
12
12
 
13
- def serialize(obj)
14
- Walker.walk(@schema, obj, true, @options)
13
+ def serialize(data)
14
+ Walker.walk(@schema, data, true, @options)
15
+ end
16
+
17
+ DataWithContext = Struct.new(:data, :context, keyword_init: true)
18
+
19
+ module WithContext
20
+ ARG2_NOT_GIVEN = :__json_schema_serializer_arg2_not_given__
21
+
22
+ def with_context!(arg1, arg2 = ARG2_NOT_GIVEN) # rubocop:disable Airbnb/OptArgParameters
23
+ if block_given?
24
+ DataWithContext.new(data: yield, context: arg1)
25
+ elsif arg2 == ARG2_NOT_GIVEN
26
+ DataWithContext.new(arg1)
27
+ else
28
+ DataWithContext.new(data: arg1, context: arg2)
29
+ end
30
+ end
15
31
  end
16
32
 
17
33
  class Walker
@@ -27,7 +43,27 @@ module JSON
27
43
  if options[:inject_key]
28
44
  inject_key = try_hash(schema, options[:inject_key])
29
45
  injector = try_hash(options[:injectors], inject_key) if inject_key
30
- obj = injector.new(obj) if injector
46
+ if obj.instance_of?(JSON::Schema::Serializer::DataWithContext)
47
+ options = options.merge(inject_context: obj.context)
48
+ obj = obj.data
49
+ end
50
+ if injector
51
+ if options[:inject_context]
52
+ obj =
53
+ if options[:inject_by_keyword]
54
+ injector.new(data: obj, context: options[:inject_context])
55
+ else
56
+ injector.new(obj, options[:inject_context])
57
+ end
58
+ else
59
+ obj =
60
+ if options[:inject_by_keyword]
61
+ injector.new(data: obj)
62
+ else
63
+ injector.new(obj)
64
+ end
65
+ end
66
+ end
31
67
  end
32
68
  type_coerce(schema, detect_type(type, obj), format, obj, required, options)
33
69
  end
@@ -121,7 +157,7 @@ module JSON
121
157
  when "string"
122
158
  case obj
123
159
  when nil
124
- ""
160
+ options[:null_through] ? nil : ""
125
161
  when DateTime, Date, Time, TimeWithZone
126
162
  case format.to_s
127
163
  when "date-time"
@@ -134,7 +170,7 @@ module JSON
134
170
  obj.to_s
135
171
  end
136
172
  when Regexp
137
- obj.inspect.gsub(%r`^/|/[a-z]*$`, '')
173
+ obj.inspect.gsub(%r{^/|/[a-z]*$}, "")
138
174
  else
139
175
  obj.to_s
140
176
  end
@@ -144,6 +180,10 @@ module JSON
144
180
  1
145
181
  when false
146
182
  0
183
+ when nil
184
+ options[:null_through] ? nil : 0
185
+ when ""
186
+ options[:empty_string_number_coerce_null] ? nil : 0
147
187
  else
148
188
  obj.to_i
149
189
  end
@@ -153,28 +193,55 @@ module JSON
153
193
  1.0
154
194
  when false
155
195
  0.0
196
+ when nil
197
+ options[:null_through] ? nil : 0.0
198
+ when ""
199
+ options[:empty_string_number_coerce_null] ? nil : 0.0
156
200
  else
157
201
  obj.to_f
158
202
  end
159
203
  when "boolean"
160
- obj == true
204
+ if obj.nil? && options[:null_through]
205
+ nil
206
+ elsif options[:empty_string_boolean_coerce_null] && obj == ""
207
+ nil
208
+ elsif options[:false_values]
209
+ !options[:false_values].include?(obj)
210
+ elsif options[:no_boolean_coerce]
211
+ obj == true
212
+ else
213
+ obj ? true : false
214
+ end
161
215
  when "array"
162
216
  items_schema = try_hash(schema, :items)
163
- obj.nil? ? [] : obj.map { |item| walk(items_schema, item, true, options) }
217
+ return options[:null_through] ? nil : [] if obj.nil? || !obj.respond_to?(:map)
218
+ return options[:null_through] ? nil : [] if options[:guard_primitive_in_structure] && is_primitive?(obj)
219
+
220
+ obj.map { |item| walk(items_schema, item, true, options) }
164
221
  when "object"
222
+ return nil if obj.nil? && options[:null_through]
223
+ return options[:null_through] ? nil : {} if options[:guard_primitive_in_structure] && is_primitive?(obj)
224
+
165
225
  properties_schema = try_hash(schema, :properties)
166
226
  additional_properties_schema = try_hash(schema, :additionalProperties)
167
227
  required_schema = Set.new(try_hash(schema, :required)&.map(&:to_s))
168
- ret = properties_schema.map do |name, property_schema|
169
- [name.to_s, walk(property_schema, try_hash(obj, name), required_schema.include?(name.to_s), options)]
170
- end.to_h
228
+ input_key_transform = options[:schema_key_transform_for_input] # schema key -> input obj key
229
+ output_key_transform = options[:schema_key_transform_for_output] # schema key -> out
230
+ ret =
231
+ properties_schema.map do |name, property_schema|
232
+ input_key = input_key_transform ? input_key_transform.call(name.to_s) : name
233
+ output_key = output_key_transform ? output_key_transform.call(name.to_s) : name.to_s
234
+ [output_key, walk(property_schema, try_hash(obj, input_key), required_schema.include?(name.to_s), options)]
235
+ end.to_h
171
236
  if additional_properties_schema
172
- not_additional_keys = Set.new(properties_schema.keys.map(&:to_s))
237
+ not_additional_keys_array = properties_schema.keys.map(&:to_s)
238
+ not_additional_keys = Set.new(input_key_transform ? not_additional_keys_array.map { |k| input_key_transform.call(k) } : not_additional_keys_array)
173
239
  additional_keys = obj.keys.reject { |key| not_additional_keys.include?(key.to_s) }
174
240
  ret.merge(
175
241
  additional_keys.map do |name|
176
- [name.to_s, walk(additional_properties_schema, try_hash(obj, name), false, options)]
177
- end.to_h
242
+ output_key = output_key_transform ? output_key_transform.call(name.to_s) : name.to_s
243
+ [output_key, walk(additional_properties_schema, try_hash(obj, name), false, options)]
244
+ end.to_h,
178
245
  )
179
246
  else
180
247
  ret
@@ -186,11 +253,20 @@ module JSON
186
253
 
187
254
  def try_hash(obj, name)
188
255
  if obj.respond_to?(:"[]")
189
- obj[name] || obj[name.is_a?(Symbol) ? name.to_s : name.to_sym]
256
+ obj[name] || obj[name.is_a?(String) ? name.to_sym : name.to_s]
190
257
  elsif obj.respond_to?(name)
191
258
  obj.send(name)
192
259
  end
193
260
  end
261
+
262
+ def is_primitive?(obj)
263
+ case obj
264
+ when String, Integer, Float, true, false, nil
265
+ true
266
+ else
267
+ false
268
+ end
269
+ end
194
270
  end
195
271
  end
196
272
  end
@@ -1,7 +1,7 @@
1
- module Json
2
- module Schema
3
- module Serializer
4
- VERSION = "1.4.0".freeze
1
+ module JSON
2
+ class Schema
3
+ class Serializer
4
+ VERSION = "1.7.1".freeze
5
5
  end
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json-schema-serializer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Narazaka
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-12-01 00:00:00.000000000 Z
11
+ date: 2020-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.5'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.5'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0.18'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0.18'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rubocop
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -181,7 +195,7 @@ metadata:
181
195
  homepage_uri: https://github.com/Narazaka/json-schema-serializer
182
196
  source_code_uri: https://github.com/Narazaka/json-schema-serializer.git
183
197
  changelog_uri: https://github.com/Narazaka/json-schema-serializer/blob/master/CHANGELOG.md
184
- documentation_uri: https://www.rubydoc.info/gems/json-schema-serializer/1.4.0
198
+ documentation_uri: https://www.rubydoc.info/gems/json-schema-serializer/1.7.1
185
199
  post_install_message:
186
200
  rdoc_options: []
187
201
  require_paths: