json_mapper 0.1.1 → 0.2.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.
- data/VERSION +1 -1
- data/json_mapper.gemspec +3 -2
- data/lib/json_mapper.rb +117 -17
- data/lib/json_mapper/attribute.rb +24 -6
- data/lib/json_mapper/attribute_list.rb +7 -2
- data/lib/json_mapper/parser.rb +11 -0
- data/test/fixtures/complex.json +3 -0
- data/test/fixtures/simple.json +3 -1
- data/test/json_mapper_test.rb +50 -0
- data/test/support/models.rb +7 -0
- metadata +6 -5
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/json_mapper.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{json_mapper}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Trond Arve Nordheim"]
|
12
|
-
s.date = %q{2010-08-
|
12
|
+
s.date = %q{2010-08-09}
|
13
13
|
s.email = %q{tanordheim@gmail.com}
|
14
14
|
s.extra_rdoc_files = [
|
15
15
|
"LICENSE",
|
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
|
|
28
28
|
"lib/json_mapper.rb",
|
29
29
|
"lib/json_mapper/attribute.rb",
|
30
30
|
"lib/json_mapper/attribute_list.rb",
|
31
|
+
"lib/json_mapper/parser.rb",
|
31
32
|
"test.rb",
|
32
33
|
"test/fixtures/complex.json",
|
33
34
|
"test/fixtures/simple.json",
|
data/lib/json_mapper.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
require "json"
|
2
2
|
|
3
3
|
class Boolean; end
|
4
|
+
class DelimitedString; end
|
4
5
|
|
5
6
|
module JSONMapper
|
6
7
|
|
7
8
|
def self.included(base)
|
8
9
|
base.instance_variable_set("@attributes", {})
|
10
|
+
base.instance_variable_set("@json_data", {})
|
9
11
|
base.extend ClassMethods
|
10
12
|
end
|
11
13
|
|
@@ -13,8 +15,8 @@ module JSONMapper
|
|
13
15
|
|
14
16
|
def json_attribute(name, *args)
|
15
17
|
|
16
|
-
source_attributes, type = extract_attribute_data(name, *args)
|
17
|
-
attribute = Attribute.new(name, source_attributes, type)
|
18
|
+
source_attributes, type, options = extract_attribute_data(name, *args)
|
19
|
+
attribute = Attribute.new(name, source_attributes, type, options)
|
18
20
|
@attributes[to_s] ||= []
|
19
21
|
@attributes[to_s] << attribute
|
20
22
|
|
@@ -24,8 +26,8 @@ module JSONMapper
|
|
24
26
|
|
25
27
|
def json_attributes(name, *args)
|
26
28
|
|
27
|
-
source_attributes, type = extract_attribute_data(name, *args)
|
28
|
-
attribute = AttributeList.new(name, source_attributes, type)
|
29
|
+
source_attributes, type, options = extract_attribute_data(name, *args)
|
30
|
+
attribute = AttributeList.new(name, source_attributes, type, options)
|
29
31
|
@attributes[to_s] ||= []
|
30
32
|
@attributes[to_s] << attribute
|
31
33
|
|
@@ -37,17 +39,30 @@ module JSONMapper
|
|
37
39
|
@attributes[to_s] || []
|
38
40
|
end
|
39
41
|
|
40
|
-
def
|
42
|
+
def json_data
|
43
|
+
@json_data[to_s] || []
|
44
|
+
end
|
41
45
|
|
42
|
-
|
43
|
-
json = JSON.parse(data, { :symbolize_names => true })
|
46
|
+
def parse(data, options = {})
|
44
47
|
|
45
|
-
|
48
|
+
return nil if data.nil? || data == ""
|
49
|
+
json = get_json_structure(data, options)
|
46
50
|
parse_json(json)
|
47
51
|
|
48
52
|
end
|
49
53
|
|
54
|
+
def parse_collection(data, options = {})
|
55
|
+
|
56
|
+
return [] if data.nil? || data == ""
|
57
|
+
json = get_json_structure(data, options)
|
58
|
+
parse_json_collection(json)
|
59
|
+
|
60
|
+
end
|
61
|
+
|
50
62
|
def parse_json(json)
|
63
|
+
|
64
|
+
# Set the JSON data for this instance
|
65
|
+
@json_data[to_s] = json
|
51
66
|
|
52
67
|
# Create a new instance of ourselves
|
53
68
|
instance = new
|
@@ -67,7 +82,19 @@ module JSONMapper
|
|
67
82
|
if attribute.is_a?(AttributeList)
|
68
83
|
value = [ value ] unless value.is_a?(Array)
|
69
84
|
value.each do |v|
|
70
|
-
|
85
|
+
|
86
|
+
list_attribute = build_attribute(attribute.name, attribute.type, attribute.options)
|
87
|
+
list_attribute_value = list_attribute.typecast(v)
|
88
|
+
|
89
|
+
# Some times typecasting a value for a list will produce another list, in the case of
|
90
|
+
# for instance DelimitedString. If this is the case, we concat that array to the list.
|
91
|
+
# Otherwise, we just append the value.
|
92
|
+
if list_attribute_value.is_a?(Array)
|
93
|
+
instance.send("#{attribute.name}").concat(list_attribute_value)
|
94
|
+
else
|
95
|
+
instance.send("#{attribute.name}") << list_attribute_value
|
96
|
+
end
|
97
|
+
|
71
98
|
end
|
72
99
|
else
|
73
100
|
instance.send("#{attribute.name}=".to_sym, attribute.typecast(value))
|
@@ -79,10 +106,43 @@ module JSONMapper
|
|
79
106
|
|
80
107
|
end
|
81
108
|
|
109
|
+
def parse_json_collection(json)
|
110
|
+
|
111
|
+
collection = []
|
112
|
+
|
113
|
+
if json.is_a?(Array)
|
114
|
+
json.each do |element|
|
115
|
+
collection << parse_json(element)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
collection
|
120
|
+
|
121
|
+
end
|
122
|
+
|
82
123
|
private
|
83
124
|
|
84
|
-
def
|
85
|
-
|
125
|
+
def get_json_structure(data, options = {})
|
126
|
+
|
127
|
+
# Parse the data into a hash
|
128
|
+
json = Parser.parse(data)
|
129
|
+
|
130
|
+
# If we need to shift the structure, do that now
|
131
|
+
shift = options.delete(:shift)
|
132
|
+
unless shift.nil?
|
133
|
+
shift = [ shift ] unless shift.is_a?(Array)
|
134
|
+
shift.each do |s|
|
135
|
+
break unless json.key?(s) # Break out if we can't find the element we're looking for
|
136
|
+
json = json[s]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
json
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
def build_attribute(name, type, options)
|
145
|
+
Attribute.new(name, name, type, options)
|
86
146
|
end
|
87
147
|
|
88
148
|
def extract_attribute_data(name, *args)
|
@@ -94,10 +154,10 @@ module JSONMapper
|
|
94
154
|
raise ArgumentError.new("Type parameter is required")
|
95
155
|
end
|
96
156
|
|
97
|
-
# If the first argument is a symbol or an array, that's
|
157
|
+
# If the first argument is a symbol, string or an array, that's
|
98
158
|
# a specific source attribute mapping. If not, use the
|
99
159
|
# specified name as the source attribute name.
|
100
|
-
if args[0].is_a?(Symbol) || args[0].is_a?(Array)
|
160
|
+
if args[0].is_a?(Symbol) || args[0].is_a?(Array) || args[0].is_a?(String) || args[0].is_a?(Hash)
|
101
161
|
source_attributes = args.delete_at(0)
|
102
162
|
else
|
103
163
|
source_attributes = name
|
@@ -105,19 +165,40 @@ module JSONMapper
|
|
105
165
|
|
106
166
|
# The remaining first argument must be a valid data type
|
107
167
|
if args[0].is_a?(Class)
|
108
|
-
type = args
|
168
|
+
type = args.delete_at(0)
|
109
169
|
else
|
110
170
|
raise ArgumentError.new("Invalid type parameter specified")
|
111
171
|
end
|
112
172
|
|
113
|
-
|
173
|
+
# If we have anything remaining, and it's a hash, use it as our options
|
174
|
+
options = {}
|
175
|
+
if !args.empty? && args.first.is_a?(Hash)
|
176
|
+
options = args.delete_at(0)
|
177
|
+
end
|
178
|
+
|
179
|
+
return source_attributes, type, options
|
114
180
|
|
115
181
|
end
|
116
182
|
|
117
183
|
def is_mapped?(attribute, json)
|
118
184
|
|
185
|
+
# Just return true if this attribute is potentially self-referencing
|
186
|
+
return true if attribute.self_referential?
|
187
|
+
|
188
|
+
# Return false if our JSON isn't a hash or an array
|
189
|
+
return false unless json.is_a?(Hash) || json.is_a?(Array)
|
190
|
+
|
119
191
|
attribute.source_attributes.each do |source_attribute|
|
120
|
-
|
192
|
+
|
193
|
+
# If the source attribute is a hash, do a key/value lookup on the json data
|
194
|
+
if source_attribute.is_a?(Hash)
|
195
|
+
|
196
|
+
source_key = source_attribute.keys.first
|
197
|
+
if json.key?(source_key) && json[source_key].is_a?(Hash) && json[source_key].key?(source_attribute[source_key])
|
198
|
+
return true
|
199
|
+
end
|
200
|
+
|
201
|
+
elsif json.key?(source_attribute)
|
121
202
|
return true
|
122
203
|
end
|
123
204
|
end
|
@@ -127,11 +208,29 @@ module JSONMapper
|
|
127
208
|
|
128
209
|
def mapping_value(attribute, json)
|
129
210
|
|
211
|
+
# Return nil if our JSON isn't a hash or an array
|
212
|
+
return nil unless json.is_a?(Hash) || json.is_a?(Array)
|
213
|
+
|
130
214
|
attribute.source_attributes.each do |source_attribute|
|
131
|
-
|
215
|
+
|
216
|
+
# If the source attribute is a hash, do a key/value lookup on the json data
|
217
|
+
if source_attribute.is_a?(Hash)
|
218
|
+
|
219
|
+
source_key = source_attribute.keys.first
|
220
|
+
if json.key?(source_key) && json[source_key].key?(source_attribute[source_key])
|
221
|
+
return json[source_key][source_attribute[source_key]]
|
222
|
+
end
|
223
|
+
|
224
|
+
elsif json.key?(source_attribute)
|
132
225
|
return json[source_attribute]
|
133
226
|
end
|
227
|
+
|
134
228
|
end
|
229
|
+
|
230
|
+
# If no mapping could be found and this attribute is potentially
|
231
|
+
# self-referencing, return the current JSON data as the mapped value
|
232
|
+
return json_data if attribute.self_referential?
|
233
|
+
|
135
234
|
return nil
|
136
235
|
|
137
236
|
end
|
@@ -140,5 +239,6 @@ module JSONMapper
|
|
140
239
|
|
141
240
|
end
|
142
241
|
|
242
|
+
require "json_mapper/parser"
|
143
243
|
require "json_mapper/attribute"
|
144
244
|
require "json_mapper/attribute_list"
|
@@ -1,14 +1,13 @@
|
|
1
1
|
class Attribute
|
2
2
|
|
3
|
-
attr_accessor :name, :source_attributes, :type
|
3
|
+
attr_accessor :name, :source_attributes, :type, :options
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
def initialize(name, source_attributes, type)
|
5
|
+
def initialize(name, source_attributes, type, options = {})
|
8
6
|
|
9
7
|
self.name = name
|
10
8
|
self.source_attributes = source_attributes.is_a?(Array) ? source_attributes : [ source_attributes ]
|
11
9
|
self.type = type
|
10
|
+
self.options = options
|
12
11
|
|
13
12
|
end
|
14
13
|
|
@@ -16,13 +15,32 @@ class Attribute
|
|
16
15
|
@method_name ||= self.name.to_s.tr("-", "_")
|
17
16
|
end
|
18
17
|
|
18
|
+
def self_referential?
|
19
|
+
self.source_attributes.include?("self")
|
20
|
+
end
|
21
|
+
|
19
22
|
def typecast(value)
|
20
23
|
|
21
24
|
return value if value.nil?
|
22
25
|
|
23
26
|
if self.type == String then return value.to_s
|
24
|
-
elsif self.type ==
|
25
|
-
|
27
|
+
elsif self.type == DelimitedString
|
28
|
+
self.options[:delimiter] ||= ","
|
29
|
+
return value.split(self.options[:delimiter])
|
30
|
+
elsif self.type == Integer
|
31
|
+
begin
|
32
|
+
return value.to_i
|
33
|
+
rescue
|
34
|
+
return nil
|
35
|
+
end
|
36
|
+
elsif self.type == Float
|
37
|
+
begin
|
38
|
+
return value.to_f
|
39
|
+
rescue
|
40
|
+
return nil
|
41
|
+
end
|
42
|
+
elsif self.type == Boolean then return %w(true t 1).include?(value.to_s.downcase)
|
43
|
+
elsif self.type == DateTime then return Date.parse(value.to_s)
|
26
44
|
else
|
27
45
|
|
28
46
|
# If our type is a JSONMapper instance, delegate the
|
@@ -1,12 +1,13 @@
|
|
1
1
|
class AttributeList < ::Array
|
2
2
|
|
3
|
-
attr_accessor :name, :source_attributes, :type
|
3
|
+
attr_accessor :name, :source_attributes, :type, :options
|
4
4
|
|
5
|
-
def initialize(name, source_attributes, type)
|
5
|
+
def initialize(name, source_attributes, type, options = {})
|
6
6
|
|
7
7
|
self.name = name
|
8
8
|
self.source_attributes = source_attributes.is_a?(Array) ? source_attributes : [ source_attributes ]
|
9
9
|
self.type = type
|
10
|
+
self.options = options
|
10
11
|
|
11
12
|
end
|
12
13
|
|
@@ -16,5 +17,9 @@ class AttributeList < ::Array
|
|
16
17
|
|
17
18
|
def typecast
|
18
19
|
end
|
20
|
+
|
21
|
+
def self_referential?
|
22
|
+
false # Attribute lists can't be self referential
|
23
|
+
end
|
19
24
|
|
20
25
|
end
|
data/test/fixtures/complex.json
CHANGED
data/test/fixtures/simple.json
CHANGED
data/test/json_mapper_test.rb
CHANGED
@@ -42,8 +42,10 @@ class JSONMapperTest < Test::Unit::TestCase
|
|
42
42
|
should "parse simple json structure into a ruby object" do
|
43
43
|
model = SimpleModel.parse(fixture_file("simple.json"))
|
44
44
|
model.id.should == 1
|
45
|
+
model.money.should == 125.50
|
45
46
|
model.title.should == "Simple JSON title"
|
46
47
|
model.boolean.should == true
|
48
|
+
model.datetime.should == Date.parse("2010-10-08 17:59:46")
|
47
49
|
end
|
48
50
|
|
49
51
|
should "assign value from different sources into an attribute" do
|
@@ -92,6 +94,7 @@ class JSONMapperTest < Test::Unit::TestCase
|
|
92
94
|
model.model_title.should == "Complex JSON title"
|
93
95
|
model.simple.id.should == 1
|
94
96
|
model.simple.title.should == "Simple JSON title"
|
97
|
+
model.nested_test.should == "foo bar"
|
95
98
|
model.simples.size.should == 2
|
96
99
|
model.simples.first.id.should == 1
|
97
100
|
model.simples.first.title.should == "Simple JSON title #1"
|
@@ -99,6 +102,53 @@ class JSONMapperTest < Test::Unit::TestCase
|
|
99
102
|
model.simples.last.title.should == "Simple JSON title #2"
|
100
103
|
end
|
101
104
|
|
105
|
+
should "be able to shift into a data structure to find the root element" do
|
106
|
+
|
107
|
+
json = '{ "foo": { "id": 1 } }'
|
108
|
+
model = SimpleModel.parse(json, :shift => :foo)
|
109
|
+
model.id.should == 1
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
should "be able to shift deep into a data structure to find the root element" do
|
114
|
+
|
115
|
+
json = '{ "foo": { "bar": { "id": 1 } } }'
|
116
|
+
model = SimpleModel.parse(json, :shift => [ :foo, :bar ])
|
117
|
+
model.id.should == 1
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
should "generate a collection of objects from an array" do
|
122
|
+
|
123
|
+
json = '[ { "id": 1 }, { "id": 2 } ]'
|
124
|
+
models = SimpleModel.parse_collection(json)
|
125
|
+
models.size.should == 2
|
126
|
+
|
127
|
+
models.first.id.should == 1
|
128
|
+
models.last.id.should == 2
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
should "be able to use a delimited string as an array" do
|
133
|
+
|
134
|
+
json = '{ "delimited": "foo,bar,baz" }'
|
135
|
+
model = ComplexModel.parse(json)
|
136
|
+
model.delimited.size.should == 3
|
137
|
+
model.delimited[0].should == "foo"
|
138
|
+
model.delimited[1].should == "bar"
|
139
|
+
model.delimited[2].should == "baz"
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
should "be able to map an object to self" do
|
144
|
+
|
145
|
+
json = '{ "id": 1 }'
|
146
|
+
model = ComplexModel.parse(json)
|
147
|
+
model.id.should == 1
|
148
|
+
model.self_referential.id.should == 1
|
149
|
+
|
150
|
+
end
|
151
|
+
|
102
152
|
end
|
103
153
|
|
104
154
|
end
|
data/test/support/models.rb
CHANGED
@@ -3,8 +3,10 @@ class SimpleModel
|
|
3
3
|
include JSONMapper
|
4
4
|
|
5
5
|
json_attribute :id, Integer
|
6
|
+
json_attribute :money, Float
|
6
7
|
json_attribute :title, String
|
7
8
|
json_attribute :boolean, Boolean
|
9
|
+
json_attribute :datetime, DateTime
|
8
10
|
|
9
11
|
end
|
10
12
|
|
@@ -14,8 +16,13 @@ class ComplexModel
|
|
14
16
|
|
15
17
|
json_attribute :id, [ :id, :attribute_id ], Integer
|
16
18
|
json_attribute :model_title, :title, String
|
19
|
+
json_attribute :datetime, DateTime
|
17
20
|
json_attribute :simple, SimpleModel
|
21
|
+
json_attribute :nested_test, { :nested => :test }, String
|
18
22
|
json_attributes :simples, SimpleModel
|
19
23
|
json_attributes :integers, Integer
|
24
|
+
json_attributes :delimited, DelimitedString, :delimiter => ","
|
25
|
+
|
26
|
+
json_attribute :self_referential, "self", SimpleModel
|
20
27
|
|
21
28
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: json_mapper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Trond Arve Nordheim
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-08-
|
18
|
+
date: 2010-08-09 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -104,6 +104,7 @@ files:
|
|
104
104
|
- lib/json_mapper.rb
|
105
105
|
- lib/json_mapper/attribute.rb
|
106
106
|
- lib/json_mapper/attribute_list.rb
|
107
|
+
- lib/json_mapper/parser.rb
|
107
108
|
- test.rb
|
108
109
|
- test/fixtures/complex.json
|
109
110
|
- test/fixtures/simple.json
|