json-schema-serializer 1.5.2 → 2.1.0

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: d6ac2c2fbbc49625b87c22bba8061ef8aacff924a4e1bfa19faedbeeb3d6fcb7
4
- data.tar.gz: 4071e3b87030dc4bff3f5800a650618619b703121094afea5b9cae8b1f584689
3
+ metadata.gz: 688a9d9a5604e822768c5427964b4b63a4bc1e4a909709672a4ba091c9afd309
4
+ data.tar.gz: 842ed003063daf2b04c14cee4239d8b2b31c4bdc77426adbc3c231ac80922576
5
5
  SHA512:
6
- metadata.gz: c5372d62cecbea2832b93c09b4c3c7336a54d52e342f23d7c8689266d2b848d94ffb218cb19464ba16d74464da6ccc69c4706127f85acfa383f24cbb209d9cf0
7
- data.tar.gz: 5426726b5f922839c4e21b0172a1f9281167bc2c5f41ef18baa81995d2395949624627f6b786f2c764715fabef7ba299f21d316a40d4b456543755b9b68eb3e3
6
+ metadata.gz: 88ede4a87f75b6f176d177cb078d7336ba67f25923d890a95c727106a14e2c673f6a02593056a3f069b30d517b6b48f344075df7b30e0edac76e1bab58f4fb15
7
+ data.tar.gz: f797603f80e47cced28a52bddabf61cef290064339d8b25c32b89fd7ebafd2093c46eeaa9b79f6436b557d8315ec23c5153060058f364067f98243cf3222d924
@@ -1,5 +1,19 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 2.0.0
4
+
5
+ - fix: Non-primitive (arrays, objects, etc.) "default" schemas and injections have strange results.
6
+ - If you were using a non-primitive (array, object, etc.) "default" schema with injection, it could be a breaking change!
7
+
8
+ ## 1.7.0
9
+
10
+ - add: `with_context!` api
11
+ - add: `:inject_by_keyword` option
12
+
13
+ ## 1.6.0
14
+
15
+ - add: inject context
16
+
3
17
  ## 1.5.0
4
18
 
5
19
  - add: many options
data/README.md CHANGED
@@ -112,6 +112,178 @@ 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"
@@ -211,12 +383,18 @@ new({
211
383
  }, { schema_key_transform_for_output: ->(name) { name.underscore } }).serialize({ userCount: 1 }) == { "user_count" => 1 }
212
384
  ```
213
385
 
214
- #### options[:injectors] [Hashlike<String, Class>, Class], options[:inject_key] [String, Symbol]
386
+ #### options[:injectors] [Hashlike<String, Class>, Class], options[:inject_key] [String, Symbol], options[:inject_context] [any], options[:inject_by_keyword] [Boolean]
215
387
 
216
388
  If schema has inject key, the serializer treats data by `injectors[inject_key].new(data)` (or `injectors.send(inject_key).new(data)`).
217
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
+
218
394
  See examples in [Usage](#usage).
219
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
+
220
398
  #### options[:null_through] [Boolean]
221
399
 
222
400
  If data is null, always serialize null.
@@ -275,6 +453,14 @@ Serialize the object data by the schema.
275
453
 
276
454
  Serialize target object. The serializer tries data["foo"], data[:foo] and data.foo!
277
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
+
278
464
  ## License
279
465
 
280
466
  Zlib License
@@ -0,0 +1,5 @@
1
+ target :lib do
2
+ signature "sig"
3
+
4
+ check "lib"
5
+ end
File without changes
data/bin/fmt CHANGED
File without changes
data/bin/setup CHANGED
File without changes
@@ -28,13 +28,15 @@ 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"
37
38
  spec.add_development_dependency "rubocop-config-prettier", "~> 0.1"
38
39
  spec.add_development_dependency "pry-byebug", "~> 3.7"
39
40
  spec.add_development_dependency "yard", "~> 0.9"
41
+ spec.add_development_dependency "steep", ">= 0.39"
40
42
  end
@@ -5,31 +5,79 @@ require "set"
5
5
  module JSON
6
6
  class Schema
7
7
  class Serializer
8
- def initialize(schema, options = nil)
9
- @schema = options && options[:resolver] ? options[:resolver].call(schema) : schema
8
+ def initialize(schema, options = nil) # rubocop:disable Airbnb/OptArgParameters
9
+ @schema =
10
+ if options && (resolver = options[:resolver])
11
+ resolver.call(schema)
12
+ else
13
+ schema
14
+ end
10
15
  @options = options || {}
11
16
  end
12
17
 
13
18
  def serialize(data)
14
- Walker.walk(@schema, data, true, @options)
19
+ Walker.walk(@schema, data, true, false, @options)
20
+ end
21
+
22
+ DataWithContext = Struct.new(:data, :context, keyword_init: true)
23
+
24
+ module WithContext
25
+ ARG2_NOT_GIVEN = :__json_schema_serializer_arg2_not_given__
26
+
27
+ def with_context!(arg1, arg2 = ARG2_NOT_GIVEN) # rubocop:disable Airbnb/OptArgParameters
28
+ if block_given?
29
+ DataWithContext.new(data: yield, context: arg1)
30
+ elsif arg2 == ARG2_NOT_GIVEN
31
+ DataWithContext.new(arg1)
32
+ else
33
+ DataWithContext.new(data: arg1, context: arg2)
34
+ end
35
+ end
15
36
  end
16
37
 
17
38
  class Walker
18
39
  class << self
19
40
  TimeWithZone = defined?(ActiveSupport::TimeWithZone) ? ActiveSupport::TimeWithZone : nil
20
41
 
21
- def walk(schema, obj, required, options)
42
+ def walk(schema, obj, required, using_default, options)
22
43
  type = try_hash(schema, :type)
23
44
  default = try_hash(schema, :default)
24
45
  format = try_hash(schema, :format)
25
- obj = default if obj.nil?
46
+ if obj.nil?
47
+ using_default = true
48
+ obj = default
49
+ end
26
50
 
27
51
  if options[:inject_key]
28
52
  inject_key = try_hash(schema, options[:inject_key])
29
53
  injector = try_hash(options[:injectors], inject_key) if inject_key
30
- obj = injector.new(obj) if injector
54
+ if obj.instance_of?(JSON::Schema::Serializer::DataWithContext)
55
+ options = options.merge(inject_context: obj.context)
56
+ obj = obj.data
57
+ if obj.nil?
58
+ using_default = true
59
+ obj = default
60
+ end
61
+ end
62
+ if injector && !using_default
63
+ if options[:inject_context]
64
+ obj =
65
+ if options[:inject_by_keyword]
66
+ injector.new(data: obj, context: options[:inject_context])
67
+ else
68
+ injector.new(obj, options[:inject_context])
69
+ end
70
+ else
71
+ obj =
72
+ if options[:inject_by_keyword]
73
+ injector.new(data: obj)
74
+ else
75
+ injector.new(obj)
76
+ end
77
+ end
78
+ end
31
79
  end
32
- type_coerce(schema, detect_type(type, obj), format, obj, required, options)
80
+ type_coerce(schema, detect_type(type, obj), format, obj, required, using_default, options)
33
81
  end
34
82
 
35
83
  def detect_type(type, obj)
@@ -112,7 +160,7 @@ module JSON
112
160
  end
113
161
  end
114
162
 
115
- def type_coerce(schema, type, format, obj, required, options)
163
+ def type_coerce(schema, type, format, obj, required, using_default, options)
116
164
  return nil if !required && obj.nil?
117
165
 
118
166
  case type.to_s
@@ -169,8 +217,8 @@ module JSON
169
217
  nil
170
218
  elsif options[:empty_string_boolean_coerce_null] && obj == ""
171
219
  nil
172
- elsif options[:false_values]
173
- !options[:false_values].include?(obj)
220
+ elsif (false_values = options[:false_values])
221
+ !false_values.include?(obj)
174
222
  elsif options[:no_boolean_coerce]
175
223
  obj == true
176
224
  else
@@ -181,7 +229,7 @@ module JSON
181
229
  return options[:null_through] ? nil : [] if obj.nil? || !obj.respond_to?(:map)
182
230
  return options[:null_through] ? nil : [] if options[:guard_primitive_in_structure] && is_primitive?(obj)
183
231
 
184
- obj.map { |item| walk(items_schema, item, true, options) }
232
+ obj.map { |item| walk(items_schema, item, true, using_default, options) }
185
233
  when "object"
186
234
  return nil if obj.nil? && options[:null_through]
187
235
  return options[:null_through] ? nil : {} if options[:guard_primitive_in_structure] && is_primitive?(obj)
@@ -195,7 +243,7 @@ module JSON
195
243
  properties_schema.map do |name, property_schema|
196
244
  input_key = input_key_transform ? input_key_transform.call(name.to_s) : name
197
245
  output_key = output_key_transform ? output_key_transform.call(name.to_s) : name.to_s
198
- [output_key, walk(property_schema, try_hash(obj, input_key), required_schema.include?(name.to_s), options)]
246
+ [output_key, walk(property_schema, try_hash(obj, input_key), required_schema.include?(name.to_s), using_default, options)]
199
247
  end.to_h
200
248
  if additional_properties_schema
201
249
  not_additional_keys_array = properties_schema.keys.map(&:to_s)
@@ -204,7 +252,7 @@ module JSON
204
252
  ret.merge(
205
253
  additional_keys.map do |name|
206
254
  output_key = output_key_transform ? output_key_transform.call(name.to_s) : name.to_s
207
- [output_key, walk(additional_properties_schema, try_hash(obj, name), false, options)]
255
+ [output_key, walk(additional_properties_schema, try_hash(obj, name), false, using_default, options)]
208
256
  end.to_h,
209
257
  )
210
258
  else
@@ -1,7 +1,7 @@
1
1
  module JSON
2
2
  class Schema
3
3
  class Serializer
4
- VERSION = "1.5.2".freeze
4
+ VERSION = "2.1.0".freeze
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,59 @@
1
+ type jsonSchema = untyped
2
+ type jsonData = untyped
3
+ type jsonSchemaContext = untyped
4
+ type jsonSingleType = "null" | "string" | "number" | "integer" | "boolean" | "array" | "object"
5
+ type jsonType = jsonSingleType | Array[jsonSingleType]
6
+
7
+ # Classes
8
+ module JSON
9
+ class Schema
10
+ class Serializer
11
+ VERSION: String
12
+
13
+ type serializerOptions = {
14
+ inject_key: String | Symbol?,
15
+ inject_context: jsonSchemaContext?,
16
+ injectors: Hash[String | Symbol, Class] | Class,
17
+ inject_by_keyword: bool?,
18
+ resolver: (^(jsonData data) -> jsonData)?,
19
+ null_through: bool?,
20
+ empty_string_number_coerce_null: bool?,
21
+ empty_string_boolean_coerce_null: bool?,
22
+ false_values: Enumerable[untyped]?,
23
+ no_boolean_coerce: bool?,
24
+ guard_primitive_in_structure: bool?,
25
+ schema_key_transform_for_input: (^(String name) -> String)?,
26
+ schema_key_transform_for_output: (^(String name) -> String)?
27
+ }
28
+ @schema: jsonSchema
29
+ @options: serializerOptions
30
+
31
+ def initialize: (jsonSchema schema, ?serializerOptions options) -> void
32
+ def serialize: (jsonData data) -> untyped
33
+
34
+ class DataWithContext < Struct[untyped]
35
+ attr_accessor data(): jsonData
36
+ attr_accessor context(): jsonSchemaContext
37
+ def initialize: (data: jsonData, context: jsonSchemaContext) -> void
38
+ end
39
+
40
+ module WithContext
41
+ ARG2_NOT_GIVEN: :__json_schema_serializer_arg2_not_given__
42
+
43
+ def with_context!: (jsonSchemaContext context) { () -> jsonData } -> DataWithContext
44
+ | (jsonData data, jsonSchemaContext context) -> DataWithContext
45
+ | ({ data: jsonData, context: jsonSchemaContext } arg) -> DataWithContext
46
+ end
47
+
48
+ class Walker
49
+ TimeWithZone: nil
50
+
51
+ def self.walk: (jsonSchema schema, jsonData obj, bool required, bool using_default, serializerOptions options) -> untyped
52
+ def self.detect_type: (jsonType type_, jsonData obj) -> jsonSingleType
53
+ def self.type_coerce: (jsonSchema schema, jsonType type_, String format, jsonData obj, bool required, bool using_default, serializerOptions options) -> untyped
54
+ def self.try_hash: (untyped obj, String | Symbol? name) -> untyped
55
+ def self.is_primitive?: (untyped obj) -> bool
56
+ end
57
+ end
58
+ end
59
+ 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.5.2
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Narazaka
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-12-03 00:00:00.000000000 Z
11
+ date: 2020-12-26 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
@@ -150,7 +164,21 @@ dependencies:
150
164
  - - "~>"
151
165
  - !ruby/object:Gem::Version
152
166
  version: '0.9'
153
- description:
167
+ - !ruby/object:Gem::Dependency
168
+ name: steep
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0.39'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0.39'
181
+ description:
154
182
  email:
155
183
  - info@narazaka.net
156
184
  executables: []
@@ -168,12 +196,14 @@ files:
168
196
  - LICENSE
169
197
  - README.md
170
198
  - Rakefile
199
+ - Steepfile
171
200
  - bin/console
172
201
  - bin/fmt
173
202
  - bin/setup
174
203
  - json-schema-serializer.gemspec
175
204
  - lib/json/schema/serializer.rb
176
205
  - lib/json/schema/serializer/version.rb
206
+ - sig/json/schema/serializer.rbs
177
207
  homepage: https://github.com/Narazaka/json-schema-serializer
178
208
  licenses:
179
209
  - Zlib
@@ -181,8 +211,8 @@ metadata:
181
211
  homepage_uri: https://github.com/Narazaka/json-schema-serializer
182
212
  source_code_uri: https://github.com/Narazaka/json-schema-serializer.git
183
213
  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.5.2
185
- post_install_message:
214
+ documentation_uri: https://www.rubydoc.info/gems/json-schema-serializer/2.1.0
215
+ post_install_message:
186
216
  rdoc_options: []
187
217
  require_paths:
188
218
  - lib
@@ -197,9 +227,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
197
227
  - !ruby/object:Gem::Version
198
228
  version: '0'
199
229
  requirements: []
200
- rubyforge_project:
201
- rubygems_version: 2.7.6
202
- signing_key:
230
+ rubygems_version: 3.2.3
231
+ signing_key:
203
232
  specification_version: 4
204
233
  summary: JSON Schema based serializer
205
234
  test_files: []