json_parser 1.3.0 → 1.3.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
  SHA1:
3
- metadata.gz: 23023390388f2c93f237ca15e4ac5305ff793322
4
- data.tar.gz: '099a4157fc3fd9841d747de641bd3f4a5534f1e6'
3
+ metadata.gz: 54a56224cff688292d5d7fcedba88d3056155295
4
+ data.tar.gz: 8ce6c70f4cf77f9c4b9f051a126589958aee78c1
5
5
  SHA512:
6
- metadata.gz: dd160fd952cd8670ed5bbc50f59e6dadd80e8da0acafc0c18c1ef84ffd1bea3c067329712ad273752928d0985fb4779a7c2256794b6cabc6ea903a138e62ae0a
7
- data.tar.gz: b95c9fbe45504f95d2e5e9c8298b105ee2cc572da965b2c1f9c8ef93852e65da2ba49c7ef9c6bf31ec1f7e81df8ab0c18d162cc1ccafcef7ecae57041b413368
6
+ metadata.gz: afe03643bf563a8c063d59b8f0c87233918a1811feb58eee1549b5006c2ec910c8170761463a6c0756638a2a983d4041ed9968c1ab3af48c49274c6f68b8c099
7
+ data.tar.gz: 47563cda0362ea10fdf25b3ecf26ef1c370f0cd5ad8161713b7662b3ceede19fa869d0f6face2596bb31efd5490b8e84a1ac11a94e129e81bb501fbf411543dd
data/README.md CHANGED
@@ -85,8 +85,7 @@ Options
85
85
  - type: Type that the value must be cast into ([TypeCast](#typecast))
86
86
  - default: Default value (prior to casting and wrapping, see [Default](#default))
87
87
 
88
- TypeCast
89
- --------
88
+ ## TypeCast
90
89
  The type casting, when the option `type` is passed, is done through the `JsonParser::TypeCast` which can
91
90
  be extended
92
91
 
@@ -140,8 +139,7 @@ end
140
139
  #returns 1050.36
141
140
  ```
142
141
 
143
- Default
144
- -------
142
+ ## Default
145
143
  Default value returned before typecasting or class wrapping
146
144
 
147
145
  ```ruby
@@ -5,10 +5,12 @@ require 'sinclair'
5
5
  module JsonParser
6
6
  extend ActiveSupport::Concern
7
7
 
8
- autoload :TypeCast, 'json_parser/type_cast'
8
+ autoload :Builder, 'json_parser/builder'
9
+ autoload :ClassMethods, 'json_parser/class_methods'
9
10
  autoload :Crawler, 'json_parser/crawler'
10
- autoload :Wrapper, 'json_parser/wrapper'
11
+ autoload :Exception, 'json_parser/exception'
11
12
  autoload :Fetcher, 'json_parser/fetcher'
12
- autoload :ClassMethods, 'json_parser/class_methods'
13
- autoload :Builder, 'json_parser/builder'
13
+ autoload :Reader, 'json_parser/reader'
14
+ autoload :Wrapper, 'json_parser/wrapper'
15
+ autoload :TypeCast, 'json_parser/type_cast'
14
16
  end
@@ -1,45 +1,42 @@
1
1
  class JsonParser::Builder < Sinclair
2
2
 
3
- attr_reader :attr_names
3
+ attr_reader :attr_names, :path, :full_path, :cached
4
4
 
5
- def initialize(attr_names, clazz, options)
5
+ def initialize(attr_names, clazz, path: nil, full_path: nil, cached: false, **options)
6
6
  super(clazz, {
7
7
  after: false,
8
- cached: false,
9
8
  case: :lower_camel,
10
9
  class: nil,
11
10
  compact: false,
12
11
  default: nil,
13
12
  flatten: false,
14
- full_path: nil,
15
13
  json: :json,
16
- path: nil,
17
14
  type: :none
18
15
  }.merge(options.symbolize_keys))
19
16
 
20
17
  @attr_names = attr_names
18
+ @path = path
19
+ @full_path = full_path
20
+ @cached = cached
21
21
  init
22
22
  end
23
23
 
24
24
  private
25
25
 
26
- delegate :path, :full_path, :cached, :compact,
27
- :type, :after, to: :options_object
28
-
29
26
  def init
30
27
  attr_names.each do |attr|
31
28
  add_attr(attr)
32
29
  end
33
30
  end
34
31
 
35
- def json_name
36
- options[:json]
37
- end
38
-
39
32
  def real_path(attribute)
40
33
  full_path || [path, attribute].compact.join('.')
41
34
  end
42
35
 
36
+ def json_name
37
+ options[:json]
38
+ end
39
+
43
40
  def wrapper_clazz
44
41
  options[:class]
45
42
  end
@@ -48,10 +45,11 @@ class JsonParser::Builder < Sinclair
48
45
  options[:case]
49
46
  end
50
47
 
51
- def fetcher_options
48
+ def fetcher_options(attribute)
52
49
  options.slice(:compact, :after, :type, :flatten, :default).merge({
53
50
  clazz: wrapper_clazz,
54
- case_type: case_type
51
+ case_type: case_type,
52
+ path: real_path(attribute)
55
53
  })
56
54
  end
57
55
 
@@ -62,7 +60,7 @@ class JsonParser::Builder < Sinclair
62
60
  def attr_fetcher(attribute)
63
61
  <<-CODE
64
62
  ::JsonParser::Fetcher.new(
65
- #{json_name}, '#{real_path(attribute)}', self, #{fetcher_options}
63
+ #{json_name}, self, #{fetcher_options(attribute)}
66
64
  ).fetch
67
65
  CODE
68
66
  end
@@ -1,10 +1,7 @@
1
1
  module JsonParser
2
2
  module ClassMethods
3
- def json_parse(*attr_names)
4
- options = attr_names.extract_options!
5
-
6
- builder = Builder.new(attr_names, self, options)
7
- builder.build
3
+ def json_parse(*attr_names, **options)
4
+ Builder.new(attr_names, self, options).build
8
5
  end
9
6
  end
10
7
  end
@@ -1,52 +1,45 @@
1
- class JsonParser::Crawler
2
- attr_reader :post_process, :path, :case_type, :compact, :default
3
-
4
- def initialize(path, case_type: :lower_camel, compact: false, default: nil, &block)
5
- @case_type = case_type
6
- @compact = compact
7
- @default = default
8
- @path = path.map { |p| change_case(p) }
9
- @post_process = block
10
- end
11
-
12
- def crawl(json, index = 0)
13
- return wrap(json) if is_ended?(index)
14
- return crawl_array(json, index) if json.is_a? Array
1
+ module JsonParser
2
+ class Crawler
3
+ attr_reader :post_process, :path, :case_type, :compact, :default
4
+
5
+ def initialize(path:, case_type: :lower_camel, compact: false, default: nil, &block)
6
+ @case_type = case_type
7
+ @compact = compact
8
+ @default = default
9
+ @path = path
10
+ @post_process = block
11
+ end
15
12
 
16
- crawl(fetch(json, index), index + 1)
17
- rescue NoMethodError
18
- wrap(default)
19
- end
13
+ def value(json, index = 0)
14
+ crawl(json, index)
15
+ rescue Exception::KeyNotFound
16
+ wrap(default)
17
+ end
20
18
 
21
- private
19
+ private
22
20
 
23
- def fetch(json, index)
24
- key = path[index]
25
- json.key?(key) ? json[key] : json[key.to_sym]
26
- end
21
+ def crawl(json, index = 0)
22
+ return wrap(json) if reader.is_ended?(index)
23
+ return crawl_array(json, index) if json.is_a?(Array)
27
24
 
28
- def is_ended?(index)
29
- index >= path.size
30
- end
25
+ crawl(reader.read(json, index), index + 1)
26
+ end
31
27
 
32
- def wrap(json)
33
- post_process.call(json)
34
- end
28
+ def reader
29
+ @reader ||= JsonParser::Reader.new(
30
+ path: path,
31
+ case_type: case_type
32
+ )
33
+ end
35
34
 
36
- def change_case(key)
37
- case case_type
38
- when :lower_camel
39
- key.camelize(:lower)
40
- when :upper_camel
41
- key.camelize(:upper)
42
- when :snake
43
- key.underscore
35
+ def wrap(json)
36
+ post_process.call(json)
44
37
  end
45
- end
46
38
 
47
- def crawl_array(array, index)
48
- array.map { |j| crawl(j, index) }.tap do |a|
49
- a.compact! if compact
39
+ def crawl_array(array, index)
40
+ array.map { |j| value(j, index) }.tap do |a|
41
+ a.compact! if compact
42
+ end
50
43
  end
51
44
  end
52
45
  end
@@ -0,0 +1,3 @@
1
+ module JsonParser::Exception
2
+ class KeyNotFound < StandardError; end
3
+ end
@@ -5,9 +5,8 @@ class JsonParser::Fetcher
5
5
 
6
6
  delegate :after, :flatten, to: :options_object
7
7
  delegate :wrap, to: :wrapper
8
- delegate :crawl, to: :crawler
9
8
 
10
- def initialize(json, path, instance, options = {})
9
+ def initialize(json, instance, path:, **options)
11
10
  @path = path.to_s.split('.')
12
11
  @json = json
13
12
  @instance = instance
@@ -15,7 +14,7 @@ class JsonParser::Fetcher
15
14
  end
16
15
 
17
16
  def fetch
18
- value = crawl(json)
17
+ value = crawler.value(json)
19
18
  value.flatten! if flatten && value.respond_to?(:flatten!)
20
19
  value = instance.send(after, value) if after
21
20
  value
@@ -28,13 +27,13 @@ class JsonParser::Fetcher
28
27
  end
29
28
 
30
29
  def buidl_crawler
31
- JsonParser::Crawler.new(path, crawler_options) do |value|
30
+ JsonParser::Crawler.new(crawler_options) do |value|
32
31
  wrap(value)
33
32
  end
34
33
  end
35
34
 
36
35
  def crawler_options
37
- options.slice(:case_type, :compact, :default)
36
+ options.slice(:case_type, :compact, :default).merge(path: path)
38
37
  end
39
38
 
40
39
  def wrapper
@@ -0,0 +1,44 @@
1
+ module JsonParser
2
+ class Reader
3
+ attr_reader :path, :case_type
4
+
5
+ def initialize(path:, case_type:)
6
+ @case_type = case_type
7
+ @path = path.map(&self.method(:change_case))
8
+ end
9
+
10
+ def read(json, index)
11
+ key = path[index]
12
+
13
+ check_key!(json, key)
14
+
15
+ json.key?(key) ? json[key] : json[key.to_sym]
16
+ end
17
+
18
+ def is_ended?(index)
19
+ index >= path.size
20
+ end
21
+
22
+ private
23
+
24
+ def check_key!(json, key)
25
+ return if has_key?(json, key)
26
+ raise Exception::KeyNotFound
27
+ end
28
+
29
+ def has_key?(json, key)
30
+ json&.key?(key) || json&.key?(key.to_sym)
31
+ end
32
+
33
+ def change_case(key)
34
+ case case_type
35
+ when :lower_camel
36
+ key.camelize(:lower)
37
+ when :upper_camel
38
+ key.camelize(:upper)
39
+ when :snake
40
+ key.underscore
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,3 +1,3 @@
1
1
  module JsonParser
2
- VERSION = '1.3.0'
2
+ VERSION = '1.3.1'
3
3
  end
@@ -8,6 +8,9 @@
8
8
  "balance": 1000.00
9
9
  }, {
10
10
  "type": "checking"
11
+ }, {
12
+ "type": "investiment",
13
+ "balance": null
11
14
  }
12
15
  ]
13
16
  }, {
@@ -0,0 +1,9 @@
1
+ {
2
+ "user": {
3
+ "name": "Robert",
4
+ "full_name": "Robert Smith",
5
+ "LoginName": "robsmith",
6
+ "birthDate": "03/07/1980",
7
+ "password_reminder": null
8
+ }
9
+ }
@@ -18,7 +18,7 @@ describe JsonParser::Builder do
18
18
  let(:instance) { clazz.new(json) }
19
19
 
20
20
  subject do
21
- described_class.new(attr_names, clazz, options)
21
+ described_class.new(attr_names, clazz, **options)
22
22
  end
23
23
 
24
24
  describe '#build' do
@@ -79,14 +79,6 @@ describe JsonParser::Builder do
79
79
  it 'fetches the value within the json' do
80
80
  expect(instance.the_name).to eq(name)
81
81
  end
82
-
83
- context 'when option key is a string' do
84
- let(:options) { { 'full_path' => 'user.name' } }
85
-
86
- it 'fetches the value within the json' do
87
- expect(instance.the_name).to eq(name)
88
- end
89
- end
90
82
  end
91
83
  end
92
84
 
@@ -102,14 +94,6 @@ describe JsonParser::Builder do
102
94
  it 'fills the new instance with the information fetched' do
103
95
  expect(instance.person.name).to eq(name)
104
96
  end
105
-
106
- context 'when option key is a string' do
107
- let(:options) { { 'class' => Person } }
108
-
109
- it 'fills the new instance with the information fetched' do
110
- expect(instance.person.name).to eq(name)
111
- end
112
- end
113
97
  end
114
98
  end
115
99
  end
@@ -2,15 +2,15 @@ require 'spec_helper'
2
2
 
3
3
  describe JsonParser::Crawler do
4
4
  let(:subject) do
5
- described_class.new path, default_options.merge(options), &block
5
+ described_class.new default_options.merge(options), &block
6
6
  end
7
7
  let(:block) { proc { |v| v } }
8
8
  let(:path) { '' }
9
- let(:default_options) { { case_type: :lower_camel} }
9
+ let(:default_options) { { path: path, case_type: :lower_camel} }
10
10
  let(:options) { {} }
11
11
  let(:json_file) { 'json_parser.json' }
12
12
  let(:json) { load_json_fixture_file(json_file) }
13
- let(:value) { subject.crawl(json) }
13
+ let(:value) { subject.value(json) }
14
14
 
15
15
  context 'when parsing with a path' do
16
16
  let(:path) { %w(user name) }
@@ -20,7 +20,7 @@ describe JsonParser::Crawler do
20
20
  end
21
21
 
22
22
  context 'when calling twice' do
23
- before { subject.crawl(json) }
23
+ before { subject.value(json) }
24
24
 
25
25
  it 'can still crawl' do
26
26
  expect(value).to eq(json['user']['name'])
@@ -52,14 +52,14 @@ describe JsonParser::Crawler do
52
52
  let(:json_file) { 'accounts_missing.json' }
53
53
 
54
54
  it 'returns the missing values as nil' do
55
- expect(value).to eq([[1000.0, nil], nil, nil])
55
+ expect(value).to eq([[1000.0, nil, nil], nil, nil])
56
56
  end
57
57
 
58
58
  context 'when setting a default' do
59
59
  let(:options) { { default: 10 } }
60
60
 
61
61
  it 'returns the missing values as default' do
62
- expect(value).to eq([[1000.0, nil], 10, 10])
62
+ expect(value).to eq([[1000.0, 10, nil], 10, 10])
63
63
  end
64
64
  end
65
65
 
@@ -180,6 +180,27 @@ describe JsonParser::Crawler do
180
180
  end
181
181
  end
182
182
 
183
+ context 'when the key last key is missing but the value is nil' do
184
+ let(:json_file) { 'person.json' }
185
+ let(:path) { %w(user nick_name) }
186
+
187
+ it 'returns the default value' do
188
+ expect(value).to eq(default_value)
189
+ end
190
+
191
+ context 'when wrapping it with a class' do
192
+ let(:block) { proc { |v| Person.new(v) } }
193
+
194
+ it 'wrap it with the class' do
195
+ expect(value).to be_a(Person)
196
+ end
197
+
198
+ it 'wraps the default value' do
199
+ expect(value.name).to eq(default_value)
200
+ end
201
+ end
202
+ end
203
+
183
204
  context 'when the node is missing but default has the same node' do
184
205
  let(:default_value) { { node: { value: 1 } } }
185
206
  let(:path) { %w(node node node) }
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe JsonParser::Fetcher do
4
4
  let(:subject) do
5
- described_class.new json, path, instance, options
5
+ described_class.new json, instance, options.merge(path: path)
6
6
  end
7
7
  let(:path) { '' }
8
8
  let(:instance) { JsonParser::Fetcher::Dummy.new }
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples 'reader fetchin value' do
4
+ it do
5
+ expect { subject.read(json, index) }.not_to raise_error
6
+ end
7
+
8
+ it do
9
+ expect(subject.read(json, index)).not_to be_nil
10
+ end
11
+
12
+ it 'returns the evaluated value' do
13
+ expect(subject.read(json, index)).to eq(expected)
14
+ end
15
+
16
+ context 'and the json has symbolized_keys' do
17
+ it 'returns the evaluated value' do
18
+ expect(subject.read(sym_json, index)).to eq(expected)
19
+ end
20
+ end
21
+ end
22
+
23
+ describe JsonParser::Reader do
24
+ subject do
25
+ described_class.new(path: path, case_type: case_type)
26
+ end
27
+
28
+ let(:path) { %w(user full_name) }
29
+ let(:json_file) { 'complete_person.json' }
30
+ let(:full_json) { load_json_fixture_file(json_file) }
31
+ let(:json) { full_json }
32
+ let(:sym_json) { json.symbolize_keys }
33
+ let(:case_type) { :snake }
34
+ let(:index) { 0 }
35
+
36
+ describe '#read' do
37
+ context 'when the key is found' do
38
+ let(:expected) { json['user'] }
39
+
40
+ it_behaves_like 'reader fetchin value'
41
+
42
+ context 'when the path case is changed' do
43
+ let(:json) { full_json['user'] }
44
+ let(:index) { 1 }
45
+
46
+ context 'to snake_case' do
47
+ let(:path) { %w(user FullName) }
48
+ let(:expected) { json['full_name'] }
49
+
50
+ it_behaves_like 'reader fetchin value'
51
+ end
52
+
53
+ context 'to upper_camel' do
54
+ let(:case_type) { :upper_camel }
55
+ let(:path) { %w(user login_name) }
56
+ let(:expected) { json['LoginName'] }
57
+
58
+ it_behaves_like 'reader fetchin value'
59
+ end
60
+
61
+ context 'to lower_camel' do
62
+ let(:case_type) { :lower_camel }
63
+ let(:path) { %w(user birth_date) }
64
+ let(:expected) { json['birthDate'] }
65
+
66
+ it_behaves_like 'reader fetchin value'
67
+ end
68
+ end
69
+
70
+ context 'when key is found but value is null' do
71
+ let(:json) { full_json['user'] }
72
+ let(:index) { 1 }
73
+ let(:path) { %w(user password_reminder) }
74
+
75
+ it do
76
+ expect(subject.read(json, index)).to be_nil
77
+ end
78
+
79
+ context 'but keys are symbol' do
80
+ it do
81
+ expect(subject.read(sym_json, index)).to be_nil
82
+ end
83
+ end
84
+ end
85
+
86
+ context 'when json has both string and symble' do
87
+ let(:path) { %w(key) }
88
+ let(:json) { { key: 'symbol', 'key' => 'string' } }
89
+
90
+ it 'fetches the string key first' do
91
+ expect(subject.read(json, index)).to eq('string')
92
+ end
93
+ end
94
+ end
95
+
96
+ context 'when the key is missing' do
97
+ let(:path) { %w(age) }
98
+
99
+ it do
100
+ expect do
101
+ subject.read(json, index)
102
+ end.to raise_error(JsonParser::Exception::KeyNotFound)
103
+ end
104
+ end
105
+ end
106
+
107
+ describe 'is_ended?' do
108
+ context 'when index is within path' do
109
+ let(:index) { 1 }
110
+
111
+ it { expect(subject.is_ended?(index)).to be_falsey }
112
+ end
113
+
114
+ context 'when index is outside path' do
115
+ let(:index) { 2 }
116
+
117
+ it { expect(subject.is_ended?(index)).to be_truthy }
118
+ end
119
+ end
120
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bidu Dev's Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-31 00:00:00.000000000 Z
11
+ date: 2018-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -143,12 +143,15 @@ files:
143
143
  - lib/json_parser/builder.rb
144
144
  - lib/json_parser/class_methods.rb
145
145
  - lib/json_parser/crawler.rb
146
+ - lib/json_parser/exception.rb
146
147
  - lib/json_parser/fetcher.rb
148
+ - lib/json_parser/reader.rb
147
149
  - lib/json_parser/type_cast.rb
148
150
  - lib/json_parser/version.rb
149
151
  - lib/json_parser/wrapper.rb
150
152
  - spec/fixtures/accounts.json
151
153
  - spec/fixtures/accounts_missing.json
154
+ - spec/fixtures/complete_person.json
152
155
  - spec/fixtures/json_parser.json
153
156
  - spec/fixtures/person.json
154
157
  - spec/integration/readme/default_spec.rb
@@ -156,6 +159,7 @@ files:
156
159
  - spec/lib/json_parser/builder_spec.rb
157
160
  - spec/lib/json_parser/crawler_spec.rb
158
161
  - spec/lib/json_parser/fetcher_spec.rb
162
+ - spec/lib/json_parser/reader_spec.rb
159
163
  - spec/lib/json_parser/wrapper_spec.rb
160
164
  - spec/lib/json_parser_spec.rb
161
165
  - spec/spec_helper.rb
@@ -198,6 +202,7 @@ summary: Json Parser
198
202
  test_files:
199
203
  - spec/fixtures/accounts.json
200
204
  - spec/fixtures/accounts_missing.json
205
+ - spec/fixtures/complete_person.json
201
206
  - spec/fixtures/json_parser.json
202
207
  - spec/fixtures/person.json
203
208
  - spec/integration/readme/default_spec.rb
@@ -205,6 +210,7 @@ test_files:
205
210
  - spec/lib/json_parser/builder_spec.rb
206
211
  - spec/lib/json_parser/crawler_spec.rb
207
212
  - spec/lib/json_parser/fetcher_spec.rb
213
+ - spec/lib/json_parser/reader_spec.rb
208
214
  - spec/lib/json_parser/wrapper_spec.rb
209
215
  - spec/lib/json_parser_spec.rb
210
216
  - spec/spec_helper.rb