json_parser 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +4 -9
- data/lib/concerns/options_parser.rb +9 -0
- data/lib/concerns/type_cast.rb +15 -0
- data/lib/json_parser.rb +5 -0
- data/lib/json_parser/class_methods.rb +5 -88
- data/lib/json_parser/class_methods/builder.rb +74 -0
- data/lib/json_parser/crawler.rb +53 -0
- data/lib/json_parser/fetcher.rb +25 -46
- data/lib/json_parser/version.rb +1 -1
- data/lib/json_parser/wrapper.rb +30 -0
- data/spec/lib/json_parser/crawler_spec.rb +161 -0
- data/spec/lib/json_parser/fetcher_spec.rb +74 -0
- data/spec/lib/json_parser/wrapper_spec.rb +129 -0
- data/spec/lib/json_parser_spec.rb +6 -146
- data/spec/support/shared_examples/wrapper.rb +19 -0
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa3db49e4e788b1d0842fc1a76177a420dddea7a
|
4
|
+
data.tar.gz: 2e53eee8b60b49d2c789adc5da2aa415b83528c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c501a429649f8578db081111b1db7d07475f31645764277aed8f02ec834cece16b739fe5f5769ec41dfe46c02cd58db7e147fcf361a82114f616fa60b04c7264
|
7
|
+
data.tar.gz: bdf68e4a5fb504178908b51bbc992028b0442c4f9009db32c1e85c6f9de29cc13aeb676529e07c9937691708b150364a094dfc9316e959b4c6807cafc5520a56
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,14 +1,7 @@
|
|
1
|
-
GIT
|
2
|
-
remote: git@github.com:Bidu/safe_attribute_assignment.git
|
3
|
-
revision: 7ccb0c0f56a1c5f8d3f844144d7b4148069bc2a0
|
4
|
-
specs:
|
5
|
-
safe_attribute_assignment (0.0.3)
|
6
|
-
activesupport
|
7
|
-
|
8
1
|
PATH
|
9
2
|
remote: .
|
10
3
|
specs:
|
11
|
-
json_parser (
|
4
|
+
json_parser (1.0.0)
|
12
5
|
activesupport
|
13
6
|
|
14
7
|
GEM
|
@@ -35,6 +28,8 @@ GEM
|
|
35
28
|
rspec-expectations (2.99.2)
|
36
29
|
diff-lcs (>= 1.1.3, < 2.0)
|
37
30
|
rspec-mocks (2.99.3)
|
31
|
+
safe_attribute_assignment (0.0.3)
|
32
|
+
activesupport
|
38
33
|
simplecov (0.9.1)
|
39
34
|
docile (~> 1.1.0)
|
40
35
|
multi_json (~> 1.0)
|
@@ -52,5 +47,5 @@ DEPENDENCIES
|
|
52
47
|
json_parser!
|
53
48
|
rake
|
54
49
|
rspec (~> 2.14)
|
55
|
-
safe_attribute_assignment (= 0.0.3)
|
50
|
+
safe_attribute_assignment (= 0.0.3)
|
56
51
|
simplecov
|
data/lib/json_parser.rb
CHANGED
@@ -4,7 +4,12 @@ require 'active_support/all'
|
|
4
4
|
module JsonParser
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
+
require 'concerns/type_cast'
|
8
|
+
require 'concerns/options_parser'
|
7
9
|
require 'json_parser/version'
|
10
|
+
require 'json_parser/crawler'
|
11
|
+
require 'json_parser/wrapper'
|
8
12
|
require 'json_parser/fetcher'
|
9
13
|
require 'json_parser/class_methods'
|
14
|
+
require 'json_parser/class_methods/builder'
|
10
15
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module JsonParser::ClassMethods
|
2
|
+
|
2
3
|
def json_parse(*attr_names)
|
3
4
|
options = {
|
4
5
|
path: nil,
|
@@ -7,97 +8,13 @@ module JsonParser::ClassMethods
|
|
7
8
|
cached: false,
|
8
9
|
class: nil,
|
9
10
|
compact: false,
|
10
|
-
|
11
|
-
|
11
|
+
flatten: false,
|
12
|
+
after: false,
|
13
|
+
case: :lower_camel,
|
14
|
+
type: :none
|
12
15
|
}.merge(attr_names.extract_options!)
|
13
16
|
|
14
17
|
builder = Builder.new(attr_names, self, options)
|
15
18
|
builder.build
|
16
19
|
end
|
17
|
-
|
18
|
-
class Builder
|
19
|
-
attr_reader :attr_names, :options, :methods_def
|
20
|
-
|
21
|
-
def initialize(attr_names, instance, options)
|
22
|
-
@attr_names = attr_names
|
23
|
-
@instance = instance
|
24
|
-
@options = options
|
25
|
-
@methods_def = []
|
26
|
-
init
|
27
|
-
end
|
28
|
-
|
29
|
-
def build
|
30
|
-
methods_def.each do |method_def|
|
31
|
-
@instance.module_eval(method_def, __FILE__, __LINE__ + 1)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def init
|
38
|
-
attr_names.each do |attr|
|
39
|
-
add_attr(attr)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def path
|
44
|
-
options[:path]
|
45
|
-
end
|
46
|
-
|
47
|
-
def json_name
|
48
|
-
options[:json]
|
49
|
-
end
|
50
|
-
|
51
|
-
def full_path(attribute)
|
52
|
-
options[:full_path] || [path, attribute].compact.join('.')
|
53
|
-
end
|
54
|
-
|
55
|
-
def cached
|
56
|
-
options[:cached]
|
57
|
-
end
|
58
|
-
|
59
|
-
def clazz
|
60
|
-
options[:class]
|
61
|
-
end
|
62
|
-
|
63
|
-
def compact
|
64
|
-
options[:compact]
|
65
|
-
end
|
66
|
-
|
67
|
-
def after
|
68
|
-
options[:after] ? ":#{options[:after]}" : false
|
69
|
-
end
|
70
|
-
|
71
|
-
def case_type
|
72
|
-
options[:case]
|
73
|
-
end
|
74
|
-
|
75
|
-
def add_attr(attribute)
|
76
|
-
@methods_def << <<-CODE
|
77
|
-
def #{attribute}
|
78
|
-
#{cached ? cached_fetcher(attribute) : attr_fetcher(attribute)}
|
79
|
-
end
|
80
|
-
CODE
|
81
|
-
end
|
82
|
-
|
83
|
-
def attr_fetcher(attribute)
|
84
|
-
<<-CODE
|
85
|
-
JsonParser::Fetcher.new(
|
86
|
-
#{json_name}, '#{full_path(attribute)}', {
|
87
|
-
instance: self,
|
88
|
-
class: #{clazz || 'nil'},
|
89
|
-
compact: #{compact || 'false'},
|
90
|
-
after: #{after},
|
91
|
-
case_type: :#{case_type}
|
92
|
-
}
|
93
|
-
).fetch
|
94
|
-
CODE
|
95
|
-
end
|
96
|
-
|
97
|
-
def cached_fetcher(attribute)
|
98
|
-
<<-CODE
|
99
|
-
@#{attribute} ||= #{attr_fetcher(attribute)}
|
100
|
-
CODE
|
101
|
-
end
|
102
|
-
end
|
103
20
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class JsonParser::ClassMethods::Builder
|
2
|
+
include OptionsParser
|
3
|
+
|
4
|
+
attr_reader :attr_names, :methods_def
|
5
|
+
|
6
|
+
delegate :path, :cached, :compact, :type, :after, to: :options_object
|
7
|
+
|
8
|
+
def initialize(attr_names, instance, options)
|
9
|
+
@attr_names = attr_names
|
10
|
+
@instance = instance
|
11
|
+
@options = options
|
12
|
+
@methods_def = []
|
13
|
+
init
|
14
|
+
end
|
15
|
+
|
16
|
+
def build
|
17
|
+
methods_def.each do |method_def|
|
18
|
+
@instance.module_eval(method_def, __FILE__, __LINE__ + 1)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def init
|
25
|
+
attr_names.each do |attr|
|
26
|
+
add_attr(attr)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def json_name
|
31
|
+
options[:json]
|
32
|
+
end
|
33
|
+
|
34
|
+
def full_path(attribute)
|
35
|
+
options[:full_path] || [path, attribute].compact.join('.')
|
36
|
+
end
|
37
|
+
|
38
|
+
def clazz
|
39
|
+
options[:class]
|
40
|
+
end
|
41
|
+
|
42
|
+
def case_type
|
43
|
+
options[:case]
|
44
|
+
end
|
45
|
+
|
46
|
+
def fetcher_options
|
47
|
+
options.slice(:compact, :after, :type, :flatten).merge({
|
48
|
+
clazz: clazz,
|
49
|
+
case_type: case_type
|
50
|
+
})
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_attr(attribute)
|
54
|
+
@methods_def << <<-CODE
|
55
|
+
def #{attribute}
|
56
|
+
#{cached ? cached_fetcher(attribute) : attr_fetcher(attribute)}
|
57
|
+
end
|
58
|
+
CODE
|
59
|
+
end
|
60
|
+
|
61
|
+
def attr_fetcher(attribute)
|
62
|
+
<<-CODE
|
63
|
+
JsonParser::Fetcher.new(
|
64
|
+
#{json_name}, '#{full_path(attribute)}', self, #{fetcher_options}
|
65
|
+
).fetch
|
66
|
+
CODE
|
67
|
+
end
|
68
|
+
|
69
|
+
def cached_fetcher(attribute)
|
70
|
+
<<-CODE
|
71
|
+
@#{attribute} ||= #{attr_fetcher(attribute)}
|
72
|
+
CODE
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class JsonParser::Crawler
|
2
|
+
include OptionsParser
|
3
|
+
|
4
|
+
attr_reader :post_process, :path
|
5
|
+
|
6
|
+
delegate :case_type, :compact, to: :options_object
|
7
|
+
|
8
|
+
def initialize(path, options = {}, &block)
|
9
|
+
@options = options
|
10
|
+
@path = path.map { |p| change_case(p) }
|
11
|
+
@post_process = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def crawl(json, index = 0)
|
15
|
+
return nil if json.nil?
|
16
|
+
return wrap(json) if is_ended?(index)
|
17
|
+
return crawl_array(json, index) if json.is_a? Array
|
18
|
+
|
19
|
+
crawl(fetch(json, index), index + 1)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def fetch(json, index)
|
25
|
+
key = path[index]
|
26
|
+
json.key?(key) ? json[key] : json[key.to_sym]
|
27
|
+
end
|
28
|
+
|
29
|
+
def is_ended?(index)
|
30
|
+
index >= path.size
|
31
|
+
end
|
32
|
+
|
33
|
+
def wrap(json)
|
34
|
+
post_process.call(json)
|
35
|
+
end
|
36
|
+
|
37
|
+
def change_case(key)
|
38
|
+
case case_type
|
39
|
+
when :lower_camel
|
40
|
+
key.camelize(:lower)
|
41
|
+
when :upper_camel
|
42
|
+
key.camelize(:upper)
|
43
|
+
when :snake
|
44
|
+
key.underscore
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def crawl_array(array, index)
|
49
|
+
array.map { |j| crawl(j, index) }.tap do |a|
|
50
|
+
a.compact! if compact
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/json_parser/fetcher.rb
CHANGED
@@ -1,72 +1,51 @@
|
|
1
1
|
class JsonParser::Fetcher
|
2
|
-
|
2
|
+
include OptionsParser
|
3
3
|
|
4
|
-
|
4
|
+
attr_reader :path, :json, :instance
|
5
|
+
|
6
|
+
delegate :after, :flatten, to: :options_object
|
7
|
+
delegate :wrap, to: :wrapper
|
8
|
+
delegate :crawl, to: :crawler
|
9
|
+
|
10
|
+
def initialize(json, path, instance, options = {})
|
5
11
|
@path = path.to_s.split('.')
|
6
12
|
@json = json
|
13
|
+
@instance = instance
|
7
14
|
@options = options
|
8
15
|
end
|
9
16
|
|
10
17
|
def fetch
|
11
|
-
value = crawl(json
|
18
|
+
value = crawl(json)
|
19
|
+
value.flatten! if flatten && value.respond_to?(:flatten!)
|
12
20
|
value = instance.send(after, value) if after
|
13
21
|
value
|
14
22
|
end
|
15
23
|
|
16
24
|
private
|
17
25
|
|
18
|
-
def
|
19
|
-
|
20
|
-
return wrap(json) if path.empty?
|
21
|
-
return crawl_array(json, path) if json.is_a? Array
|
22
|
-
|
23
|
-
key = change_case(path[0])
|
24
|
-
value = json.key?(key) ? json[key] : json[key.to_sym]
|
25
|
-
crawl(value, path[1,path.size])
|
26
|
-
end
|
27
|
-
|
28
|
-
def change_case(key)
|
29
|
-
case case_type
|
30
|
-
when :lower_camel
|
31
|
-
key.camelize(:lower)
|
32
|
-
when :upper_camel
|
33
|
-
key.camelize(:upper)
|
34
|
-
when :snake
|
35
|
-
key.underscore
|
36
|
-
end
|
26
|
+
def crawler
|
27
|
+
@crawler ||= buidl_crawler
|
37
28
|
end
|
38
29
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
30
|
+
def buidl_crawler
|
31
|
+
JsonParser::Crawler.new(path, crawler_options) do |value|
|
32
|
+
wrap(value)
|
42
33
|
end
|
43
34
|
end
|
44
35
|
|
45
|
-
def
|
46
|
-
|
47
|
-
return clazz.new json unless json.is_a? Array
|
48
|
-
json.map { |v| wrap v }.tap do |j|
|
49
|
-
j.compact! if compact
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def clazz
|
54
|
-
options[:class]
|
55
|
-
end
|
56
|
-
|
57
|
-
def after
|
58
|
-
options[:after]
|
36
|
+
def crawler_options
|
37
|
+
options.slice(:case_type, :compact)
|
59
38
|
end
|
60
39
|
|
61
|
-
def
|
62
|
-
|
40
|
+
def wrapper
|
41
|
+
@wrapper ||= build_wrapper
|
63
42
|
end
|
64
43
|
|
65
|
-
def
|
66
|
-
|
44
|
+
def build_wrapper
|
45
|
+
JsonParser::Wrapper.new(wrapper_options)
|
67
46
|
end
|
68
47
|
|
69
|
-
def
|
70
|
-
options
|
48
|
+
def wrapper_options
|
49
|
+
options.slice(:clazz, :type)
|
71
50
|
end
|
72
|
-
end
|
51
|
+
end
|
data/lib/json_parser/version.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
class JsonParser::Wrapper
|
2
|
+
include OptionsParser
|
3
|
+
include TypeCast
|
4
|
+
|
5
|
+
delegate :clazz, :type, to: :options_object
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def wrap(value)
|
12
|
+
return value.map { |v| wrap v } if value.is_a?(Array)
|
13
|
+
|
14
|
+
value = cast(value) if has_type? && !value.nil?
|
15
|
+
return if value.nil?
|
16
|
+
|
17
|
+
value = clazz.new(value) if clazz
|
18
|
+
value
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def has_type?
|
24
|
+
type.present? && type != :none
|
25
|
+
end
|
26
|
+
|
27
|
+
def cast(value)
|
28
|
+
send("to_#{type}", value)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe JsonParser::Crawler do
|
4
|
+
let(:subject) do
|
5
|
+
described_class.new path, default_options.merge(options), &block
|
6
|
+
end
|
7
|
+
let(:block) { proc { |v| v } }
|
8
|
+
let(:path) { '' }
|
9
|
+
let(:default_options) { { case_type: :lower_camel} }
|
10
|
+
let(:options) { {} }
|
11
|
+
let(:json) { load_json_fixture_file('json_parser.json') }
|
12
|
+
let(:value) { subject.crawl(json) }
|
13
|
+
|
14
|
+
context 'when parsing with a path' do
|
15
|
+
let(:path) { %w(user name) }
|
16
|
+
|
17
|
+
it 'retrieves attribute from base json' do
|
18
|
+
expect(value).to eq(json['user']['name'])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'crawler finds a nil attribute' do
|
23
|
+
let(:path) { %w(car model) }
|
24
|
+
|
25
|
+
it 'returns nil' do
|
26
|
+
expect(value).to be_nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it do
|
30
|
+
expect { value }.not_to raise_error
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'when json is empty' do
|
35
|
+
let(:json) { nil }
|
36
|
+
let(:path) { %w(car model) }
|
37
|
+
|
38
|
+
it 'returns nil' do
|
39
|
+
expect(value).to be_nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it do
|
43
|
+
expect { value }.not_to raise_error
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'with an snake case path' do
|
48
|
+
let(:path) { ['has_money'] }
|
49
|
+
|
50
|
+
it 'returns camel cased value' do
|
51
|
+
expect(value).to eq(json['hasMoney'])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when dealing with json inside arrays' do
|
56
|
+
let(:path) { %w(animals race species name)}
|
57
|
+
let(:expected) do
|
58
|
+
['European squid', 'Macaque monkey', 'Mexican redknee tarantula']
|
59
|
+
end
|
60
|
+
|
61
|
+
it do
|
62
|
+
expect(value).to be_a(Array)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'parses them mapping arrays as sub parse' do
|
66
|
+
expect(value).to eq(expected)
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'when there are nil values' do
|
70
|
+
context 'with compact option as false' do
|
71
|
+
let(:options) { { compact: false } }
|
72
|
+
before do
|
73
|
+
json["animals"].last['race'] = nil
|
74
|
+
end
|
75
|
+
let(:expected) do
|
76
|
+
['European squid', 'Macaque monkey', nil]
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'eliminate nil values' do
|
80
|
+
expect(value).to eq(expected)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'with compact option' do
|
85
|
+
let(:options) { { compact: true } }
|
86
|
+
before do
|
87
|
+
json["animals"].last['race'] = nil
|
88
|
+
end
|
89
|
+
let(:expected) do
|
90
|
+
['European squid', 'Macaque monkey']
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'eliminate nil values' do
|
94
|
+
expect(value).to eq(expected)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'when using a snake case' do
|
101
|
+
let(:json) { { snake_cased: 'snake', snakeCased: 'Camel' }.stringify_keys }
|
102
|
+
let(:path) { [ 'snake_cased' ] }
|
103
|
+
let(:options) { { case_type: :snake } }
|
104
|
+
|
105
|
+
it 'fetches from snake cased fields' do
|
106
|
+
expect(value).to eq('snake')
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'when using a upper camel case' do
|
111
|
+
let(:json) { { UpperCase: 'upper', upperCase: 'lower' }.stringify_keys }
|
112
|
+
let(:path) { [ 'upper_case' ] }
|
113
|
+
let(:options) { { case_type: :upper_camel } }
|
114
|
+
|
115
|
+
it 'fetches from upper camel cased fields' do
|
116
|
+
expect(value).to eq('upper')
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'when using a symbol keys' do
|
121
|
+
let(:json) { load_json_fixture_file('json_parser.json').symbolize_keys }
|
122
|
+
let(:path) { [ 'id' ] }
|
123
|
+
|
124
|
+
it 'fetches from symbol keys' do
|
125
|
+
expect(value).to eq(json[:id])
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'crawler finds a nil attribute' do
|
129
|
+
let(:path) { %w(car model) }
|
130
|
+
|
131
|
+
it 'returns nil' do
|
132
|
+
expect(value).to be_nil
|
133
|
+
end
|
134
|
+
|
135
|
+
it do
|
136
|
+
expect { value }.not_to raise_error
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'when using key with false value' do
|
142
|
+
let(:path) { ['has_money'] }
|
143
|
+
before do
|
144
|
+
json['hasMoney'] = false
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'with string keys' do
|
148
|
+
it { expect(value).to be_falsey }
|
149
|
+
it { expect(value).not_to be_nil }
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'with symbol keys' do
|
153
|
+
before do
|
154
|
+
json.symbolize_keys!
|
155
|
+
end
|
156
|
+
|
157
|
+
it { expect(value).to be_falsey }
|
158
|
+
it { expect(value).not_to be_nil }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe JsonParser::Fetcher do
|
4
|
+
class JsonParser::Fetcher::Dummy
|
5
|
+
end
|
6
|
+
|
7
|
+
let(:subject) do
|
8
|
+
described_class.new json, path, instance, default_options.merge(options)
|
9
|
+
end
|
10
|
+
let(:path) { '' }
|
11
|
+
let(:default_options) { { case_type: :snake} }
|
12
|
+
let(:instance) { JsonParser::Fetcher::Dummy.new }
|
13
|
+
let(:json) { load_json_fixture_file('json_parser.json') }
|
14
|
+
let(:value) { subject.fetch }
|
15
|
+
|
16
|
+
context 'when fetching with no options' do
|
17
|
+
let(:options) { {} }
|
18
|
+
let(:path) { 'id' }
|
19
|
+
|
20
|
+
it 'retrieves attribute from base json' do
|
21
|
+
expect(value).to eq(json['id'])
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when calling the method twice' do
|
25
|
+
before do
|
26
|
+
subject.fetch
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'retrieves attribute from base json' do
|
30
|
+
expect(value).to eq(json['id'])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'when changing json value' do
|
35
|
+
let!(:old_value) { json['id'] }
|
36
|
+
before do
|
37
|
+
subject.fetch
|
38
|
+
json['id'] = 200
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'retrieves the new value' do
|
42
|
+
expect(value).not_to eq(old_value)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'flatten options' do
|
48
|
+
let(:json) { [[[1,2],[3,4]],[[5,6],[7,8]]] }
|
49
|
+
|
50
|
+
context 'when flatten option is true' do
|
51
|
+
let(:options) { { flatten: true } }
|
52
|
+
|
53
|
+
it 'returns the fetched value flattened' do
|
54
|
+
expect(subject.fetch).to eq((1..8).to_a)
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when value is not an array' do
|
58
|
+
let(:json) { 1 }
|
59
|
+
|
60
|
+
it 'returns the fetched value flattened' do
|
61
|
+
expect(subject.fetch).to eq(1)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when flatten option is false' do
|
67
|
+
let(:options) { { flatten: false } }
|
68
|
+
|
69
|
+
it 'returns the fetched value non flattened' do
|
70
|
+
expect(subject.fetch).to eq(json)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe JsonParser::Wrapper do
|
4
|
+
|
5
|
+
class JsonParser::Wrapper::DummyWrapper
|
6
|
+
attr_reader :value
|
7
|
+
def initialize(value)
|
8
|
+
@value = value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:options) { {} }
|
13
|
+
let(:subject) { described_class.new options }
|
14
|
+
let(:hash) { { a: 1 } }
|
15
|
+
|
16
|
+
describe '#wrap' do
|
17
|
+
let(:value) { hash }
|
18
|
+
let(:result) { subject.wrap(value) }
|
19
|
+
|
20
|
+
context 'with default options' do
|
21
|
+
it 'does not change the json value' do
|
22
|
+
expect(result).to eq(value)
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'with an array as value' do
|
26
|
+
let(:value) { [hash] }
|
27
|
+
|
28
|
+
it 'does not change the array' do
|
29
|
+
expect(result).to eq(value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with clazz otpion' do
|
35
|
+
let(:options) { { clazz: OpenStruct } }
|
36
|
+
|
37
|
+
it 'creates new instance from given class' do
|
38
|
+
expect(result).to be_a(OpenStruct)
|
39
|
+
expect(result.a).to eq(hash[:a])
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'with an array as value' do
|
43
|
+
let(:value) { [hash] }
|
44
|
+
|
45
|
+
it 'returns an array of objects of the given class' do
|
46
|
+
expect(result).to be_a(Array)
|
47
|
+
expect(result.first).to be_a(OpenStruct)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'with type otpion' do
|
53
|
+
let(:type) { :integer }
|
54
|
+
let(:value) { '1' }
|
55
|
+
let(:options) { { type: type } }
|
56
|
+
let(:cast) { result }
|
57
|
+
|
58
|
+
it_behaves_like 'casts basic types'
|
59
|
+
|
60
|
+
context 'when processing an array' do
|
61
|
+
let(:value) { [1.0] }
|
62
|
+
let(:cast) { result.first }
|
63
|
+
|
64
|
+
it_behaves_like 'casts basic types'
|
65
|
+
|
66
|
+
it do
|
67
|
+
expect(result).to be_a(Array)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'with nil value' do
|
72
|
+
let(:value) { nil }
|
73
|
+
|
74
|
+
it do
|
75
|
+
expect(result).to be_nil
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'when passing clazz parameter' do
|
79
|
+
let(:options) { { type: type, clazz: JsonParser::Wrapper::DummyWrapper } }
|
80
|
+
|
81
|
+
it do
|
82
|
+
expect(result).to be_nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'with blank value' do
|
88
|
+
let(:value) { '' }
|
89
|
+
|
90
|
+
it_behaves_like 'a result that is type cast', {
|
91
|
+
integer: NilClass,
|
92
|
+
float: NilClass,
|
93
|
+
string: String
|
94
|
+
}
|
95
|
+
|
96
|
+
context 'when passing clazz parameter' do
|
97
|
+
let(:options) { { type: type, clazz: JsonParser::Wrapper::DummyWrapper } }
|
98
|
+
|
99
|
+
it_behaves_like 'a result that is type cast', {
|
100
|
+
integer: NilClass,
|
101
|
+
float: NilClass,
|
102
|
+
string: JsonParser::Wrapper::DummyWrapper
|
103
|
+
}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'when passing clazz parameter' do
|
108
|
+
let(:value) { 1 }
|
109
|
+
let(:options) { { type: type, clazz: JsonParser::Wrapper::DummyWrapper } }
|
110
|
+
let(:cast) { result.value }
|
111
|
+
|
112
|
+
it_behaves_like 'casts basic types'
|
113
|
+
|
114
|
+
it 'wraps the result inside the given class' do
|
115
|
+
expect(result).to be_a(JsonParser::Wrapper::DummyWrapper)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'with none for type' do
|
120
|
+
let(:type) { :none }
|
121
|
+
let(:value) { 1.0 }
|
122
|
+
|
123
|
+
it 'does not cast the value' do
|
124
|
+
expect(result).to eq(value)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -31,14 +31,11 @@ describe JsonParser do
|
|
31
31
|
|
32
32
|
json_parse :id
|
33
33
|
json_parse :name, path: 'user'
|
34
|
-
json_parse :model, path: 'car'
|
35
34
|
json_parse :father_name, full_path: 'father.name'
|
36
35
|
json_parse :age, cached: true
|
37
|
-
json_parse :has_money
|
38
36
|
json_parse :house, class: JsonParser::House
|
39
37
|
json_parse :old_house, class: JsonParser::House, cached: true
|
40
38
|
json_parse :games, class: JsonParser::Game
|
41
|
-
json_parse :species_name, full_path: 'animals.race.species.name', compact: true
|
42
39
|
json_parse :games_filtered, class: JsonParser::Game, after: :filter_games, full_path: 'games'
|
43
40
|
|
44
41
|
def initialize(json)
|
@@ -62,27 +59,6 @@ describe JsonParser do
|
|
62
59
|
it 'retrieves attribute from base json' do
|
63
60
|
expect(value).to eq(json['id'])
|
64
61
|
end
|
65
|
-
|
66
|
-
context 'when calling the method twice' do
|
67
|
-
before do
|
68
|
-
dummy.public_send(attribute)
|
69
|
-
end
|
70
|
-
|
71
|
-
it 'retrieves attribute from base json' do
|
72
|
-
expect(value).to eq(json['id'])
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
context 'when changing json value' do
|
77
|
-
let!(:old_value) { json['id'] }
|
78
|
-
before do
|
79
|
-
dummy.id
|
80
|
-
json['id'] = 200
|
81
|
-
end
|
82
|
-
it 'retrieves the new value' do
|
83
|
-
expect(value).not_to eq(old_value)
|
84
|
-
end
|
85
|
-
end
|
86
62
|
end
|
87
63
|
|
88
64
|
context 'when parser is configured with a path' do
|
@@ -93,31 +69,6 @@ describe JsonParser do
|
|
93
69
|
end
|
94
70
|
end
|
95
71
|
|
96
|
-
context 'parser finds a nil attribute' do
|
97
|
-
let(:attribute) { :model }
|
98
|
-
|
99
|
-
it 'returns nil' do
|
100
|
-
expect(value).to be_nil
|
101
|
-
end
|
102
|
-
|
103
|
-
it do
|
104
|
-
expect { value }.not_to raise_error
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
context 'when json is instead of empty' do
|
109
|
-
let(:json) { nil }
|
110
|
-
let(:attribute) { :model }
|
111
|
-
|
112
|
-
it 'returns nil' do
|
113
|
-
expect(value).to be_nil
|
114
|
-
end
|
115
|
-
|
116
|
-
it do
|
117
|
-
expect { value }.not_to raise_error
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
72
|
context 'when configuring full path' do
|
122
73
|
let(:attribute) { :father_name }
|
123
74
|
|
@@ -139,14 +90,6 @@ describe JsonParser do
|
|
139
90
|
end
|
140
91
|
end
|
141
92
|
|
142
|
-
context 'with an snake case option' do
|
143
|
-
let(:attribute) { :has_money }
|
144
|
-
|
145
|
-
it 'returns camel cased value' do
|
146
|
-
expect(value).to eq(json['hasMoney'])
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
93
|
context 'when wrapping it with a class' do
|
151
94
|
let(:attribute) { :house }
|
152
95
|
|
@@ -211,34 +154,6 @@ describe JsonParser do
|
|
211
154
|
end
|
212
155
|
end
|
213
156
|
|
214
|
-
context 'when dealing with json inside arrays' do
|
215
|
-
let(:attribute) { :species_name }
|
216
|
-
let(:expected) do
|
217
|
-
['European squid', 'Macaque monkey', 'Mexican redknee tarantula']
|
218
|
-
end
|
219
|
-
|
220
|
-
it do
|
221
|
-
expect(value).to be_a(Array)
|
222
|
-
end
|
223
|
-
|
224
|
-
it 'parses them mapping arrays as sub parse' do
|
225
|
-
expect(value).to eq(expected)
|
226
|
-
end
|
227
|
-
|
228
|
-
context 'when there are nil values and compact is true' do
|
229
|
-
before do
|
230
|
-
json["animals"].last['race'] = nil
|
231
|
-
end
|
232
|
-
let(:expected) do
|
233
|
-
['European squid', 'Macaque monkey']
|
234
|
-
end
|
235
|
-
|
236
|
-
it 'eliminate nil values' do
|
237
|
-
expect(value).to eq(expected)
|
238
|
-
end
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
157
|
context 'when passing an after filter' do
|
243
158
|
let(:attribute) { :games_filtered }
|
244
159
|
|
@@ -247,71 +162,16 @@ describe JsonParser do
|
|
247
162
|
end
|
248
163
|
end
|
249
164
|
|
250
|
-
context 'when
|
251
|
-
class JsonParser::Dummy
|
252
|
-
json_parse :snake_cased, case: :snake
|
253
|
-
end
|
254
|
-
|
255
|
-
let(:json) { { snake_cased: 'snake', snakeCased: 'Camel' }.stringify_keys }
|
256
|
-
let(:attribute) { :snake_cased }
|
257
|
-
|
258
|
-
it 'fetches from snake cased fields' do
|
259
|
-
expect(value).to eq('snake')
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
context 'when using a upper camel case' do
|
165
|
+
context 'when casting the result' do
|
264
166
|
class JsonParser::Dummy
|
265
|
-
json_parse :
|
266
|
-
end
|
267
|
-
|
268
|
-
let(:json) { { UpperCase: 'upper', upperCase: 'lower' }.stringify_keys }
|
269
|
-
let(:attribute) { :upper_case }
|
270
|
-
|
271
|
-
it 'fetches from upper camel cased fields' do
|
272
|
-
expect(value).to eq('upper')
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
context 'when using a symbol keys' do
|
277
|
-
let(:json) { load_json_fixture_file('json_parser.json').symbolize_keys }
|
278
|
-
let(:attribute) { :id }
|
279
|
-
|
280
|
-
it 'fetches from symbol keys' do
|
281
|
-
expect(value).to eq(json[:id])
|
282
|
-
end
|
283
|
-
|
284
|
-
context 'parser finds a nil attribute' do
|
285
|
-
let(:attribute) { :model }
|
286
|
-
|
287
|
-
it 'returns nil' do
|
288
|
-
expect(value).to be_nil
|
289
|
-
end
|
290
|
-
|
291
|
-
it do
|
292
|
-
expect { value }.not_to raise_error
|
293
|
-
end
|
167
|
+
json_parse :float_value, type: :float
|
294
168
|
end
|
295
|
-
end
|
296
169
|
|
297
|
-
|
298
|
-
let(:attribute) { :
|
299
|
-
before do
|
300
|
-
json['hasMoney'] = false
|
301
|
-
end
|
170
|
+
let(:json) { { floatValue: '1' } }
|
171
|
+
let(:attribute) { :float_value }
|
302
172
|
|
303
|
-
|
304
|
-
|
305
|
-
it { expect(value).not_to be_nil }
|
306
|
-
end
|
307
|
-
|
308
|
-
context 'with symbol keys' do
|
309
|
-
before do
|
310
|
-
json.symbolize_keys!
|
311
|
-
end
|
312
|
-
|
313
|
-
it { expect(value).to be_falsey }
|
314
|
-
it { expect(value).not_to be_nil }
|
173
|
+
it do
|
174
|
+
expect(value).to be_a(Float)
|
315
175
|
end
|
316
176
|
end
|
317
177
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
shared_context 'a result that is type cast' do |types|
|
2
|
+
types.each do |type, clazz|
|
3
|
+
context "with #{type} type" do
|
4
|
+
let(:type) { type }
|
5
|
+
|
6
|
+
it do
|
7
|
+
expect(cast).to be_a(clazz)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
shared_context 'casts basic types' do
|
14
|
+
it_behaves_like 'a result that is type cast', {
|
15
|
+
integer: Integer,
|
16
|
+
float: Float,
|
17
|
+
string: String
|
18
|
+
}
|
19
|
+
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:
|
4
|
+
version: 1.0.0
|
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: 2015-
|
11
|
+
date: 2015-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -102,6 +102,7 @@ extensions: []
|
|
102
102
|
extra_rdoc_files: []
|
103
103
|
files:
|
104
104
|
- .gitignore
|
105
|
+
- .rspec
|
105
106
|
- Gemfile
|
106
107
|
- Gemfile.lock
|
107
108
|
- Guardfile
|
@@ -109,14 +110,23 @@ files:
|
|
109
110
|
- README.md
|
110
111
|
- Rakefile
|
111
112
|
- json_parser.gemspec
|
113
|
+
- lib/concerns/options_parser.rb
|
114
|
+
- lib/concerns/type_cast.rb
|
112
115
|
- lib/json_parser.rb
|
113
116
|
- lib/json_parser/class_methods.rb
|
117
|
+
- lib/json_parser/class_methods/builder.rb
|
118
|
+
- lib/json_parser/crawler.rb
|
114
119
|
- lib/json_parser/fetcher.rb
|
115
120
|
- lib/json_parser/version.rb
|
121
|
+
- lib/json_parser/wrapper.rb
|
116
122
|
- spec/fixtures/json_parser.json
|
123
|
+
- spec/lib/json_parser/crawler_spec.rb
|
124
|
+
- spec/lib/json_parser/fetcher_spec.rb
|
125
|
+
- spec/lib/json_parser/wrapper_spec.rb
|
117
126
|
- spec/lib/json_parser_spec.rb
|
118
127
|
- spec/spec_helper.rb
|
119
128
|
- spec/support/fixture_helpers.rb
|
129
|
+
- spec/support/shared_examples/wrapper.rb
|
120
130
|
homepage: https://github.com/Bidu/json_parser
|
121
131
|
licenses: []
|
122
132
|
metadata: {}
|
@@ -136,12 +146,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
136
146
|
version: '0'
|
137
147
|
requirements: []
|
138
148
|
rubyforge_project:
|
139
|
-
rubygems_version: 2.
|
149
|
+
rubygems_version: 2.4.6
|
140
150
|
signing_key:
|
141
151
|
specification_version: 4
|
142
152
|
summary: Json Parser
|
143
153
|
test_files:
|
144
154
|
- spec/fixtures/json_parser.json
|
155
|
+
- spec/lib/json_parser/crawler_spec.rb
|
156
|
+
- spec/lib/json_parser/fetcher_spec.rb
|
157
|
+
- spec/lib/json_parser/wrapper_spec.rb
|
145
158
|
- spec/lib/json_parser_spec.rb
|
146
159
|
- spec/spec_helper.rb
|
147
160
|
- spec/support/fixture_helpers.rb
|
161
|
+
- spec/support/shared_examples/wrapper.rb
|