jsoning 0.5.0 → 0.6.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/README.md +51 -0
- data/lib/jsoning/dsl/for_dsl.rb +28 -2
- data/lib/jsoning/foundations/mapper.rb +16 -12
- data/lib/jsoning/foundations/protocol.rb +42 -38
- data/lib/jsoning/foundations/version.rb +38 -0
- data/lib/jsoning/version.rb +1 -1
- data/lib/jsoning.rb +43 -8
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 69a10f48340146f1c819bd5d038088913fdd63e8
|
4
|
+
data.tar.gz: d5761009f4937cf74743679b48ffdd471bfbf3dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e6edba66976d8f2056c307ccb774f97259db82d3801eadb7c4540792a6e3a09d53327437c9b0fe83b1ec829d936d7b43efd4cb9d9a60deea1059398ec2f5f53
|
7
|
+
data.tar.gz: a36e4c08c15083b79387d72b7f2c0b2aa116e4b3c87ab0944ebfc31ce7c600c5c4347117fd7e8811e06e2dd39241b2f8d883a1ffe5cb3deaeaa2b65d361fb88d
|
data/README.md
CHANGED
@@ -7,6 +7,8 @@ any Ruby object there is. Kiss good bye to complexity
|
|
7
7
|
[](https://codeclimate.com/github/saveav/jsoning)
|
8
8
|
[ ](https://codeship.com/projects/105558)
|
9
9
|
|
10
|
+
Please refer to the test spec for better understanding. Thank you.
|
11
|
+
|
10
12
|
## Installation
|
11
13
|
|
12
14
|
Add this line to your application's Gemfile:
|
@@ -248,6 +250,51 @@ Calling: `Jsoning.parse(the_json_string, My::User)` will yield a Hash as follow:
|
|
248
250
|
"registered_at"=>"2015-11-01T14:41:09+00:00"}
|
249
251
|
```
|
250
252
|
|
253
|
+
## Versioning
|
254
|
+
|
255
|
+
Since beginning, JSONing is made to easy serializing/deserializing data from API call.
|
256
|
+
Often, API call itself can be versioned. Therefore, it would be better if JSONing
|
257
|
+
also support versioning, which it does!
|
258
|
+
|
259
|
+
By default, though, all schema are under default version. Version will be altered when
|
260
|
+
specifically modified by `version` modifier.
|
261
|
+
|
262
|
+
Suppose we have our `My::Book` to be versioned:
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
Jsoning.for(My::Book) do
|
266
|
+
version :v1 do
|
267
|
+
key :name
|
268
|
+
end
|
269
|
+
version :v2 do
|
270
|
+
key :book_name, from: :name
|
271
|
+
end
|
272
|
+
end
|
273
|
+
```
|
274
|
+
|
275
|
+
At this point, ignoring that we have ever defined `My::Book` before, `My::Book` will have
|
276
|
+
2 Jsoning versions. If we take into account the fact that we have defined `My::Book` previously,
|
277
|
+
then, we will have one additional version: the default version.
|
278
|
+
|
279
|
+
Spec below will pass:
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
book = My::Book.new("Harry Potter")
|
283
|
+
expect(Jsoning.generate(book, hash: true, version: :v1)).to eq({"name"=>"Harry Potter"})
|
284
|
+
expect(Jsoning.generate(book, hash: true, version: :v2)).to eq({"book_name"=>"Harry Potter"})
|
285
|
+
```
|
286
|
+
|
287
|
+
We can also generate the whole user json/hash, too:
|
288
|
+
|
289
|
+
```ruby
|
290
|
+
json = Jsoning.generate(user, version: :v1)
|
291
|
+
expect(JSON.parse(json)).to eq({"name"=>"Adam Baihaqi", "years_old"=>21, "gender"=>"male", "books"=>[{"name"=>"Quiet: The Power of Introvert"}, {"name"=>"Harry Potter and the Half-Blood Prince"}], "degree_detail"=>nil, "registered_at"=>"2015-11-01T14:41:09+0000"})
|
292
|
+
json = Jsoning.generate(user, version: :v2)
|
293
|
+
expect(JSON.parse(json)).to eq({"name"=>"Adam Baihaqi", "years_old"=>21, "gender"=>"male", "books"=>[{"book_name"=>"Quiet: The Power of Introvert"}, {"book_name"=>"Harry Potter and the Half-Blood Prince"}], "degree_detail"=>nil, "registered_at"=>"2015-11-01T14:41:09+0000"})
|
294
|
+
```
|
295
|
+
|
296
|
+
If for when generating object, the requested versioning is undefined, the default version will be used.
|
297
|
+
|
251
298
|
## Changelog
|
252
299
|
|
253
300
|
== Version 0.1.0
|
@@ -274,6 +321,10 @@ Calling: `Jsoning.parse(the_json_string, My::User)` will yield a Hash as follow:
|
|
274
321
|
raised due to Jsoning does not know how to extract value from them. However, if the object
|
275
322
|
is converted to JSON string, everything is working as expected.
|
276
323
|
|
324
|
+
== Version 0.6.0
|
325
|
+
|
326
|
+
1. Versioning the way JSON/Hash is de-serialized/serialized
|
327
|
+
|
277
328
|
## License
|
278
329
|
|
279
330
|
The gem is proudly available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/lib/jsoning/dsl/for_dsl.rb
CHANGED
@@ -1,10 +1,28 @@
|
|
1
1
|
class Jsoning::ForDsl
|
2
2
|
attr_reader :protocol
|
3
|
+
attr_accessor :current_version_object
|
4
|
+
@@mutex = Mutex.new
|
3
5
|
|
4
6
|
def initialize(protocol)
|
5
7
|
@protocol = protocol
|
6
8
|
end
|
7
9
|
|
10
|
+
# specify the version under which key will be executed
|
11
|
+
def version(version_name)
|
12
|
+
@@mutex.synchronize do
|
13
|
+
# todo: make sure version cannot be nester, in order that to be possible
|
14
|
+
# version must have its own dsl, dduh
|
15
|
+
# fail Jsoning::Error, "Version cannot be nested" if current_version_object
|
16
|
+
|
17
|
+
# retrieve the version, or create a new one if not yet defined
|
18
|
+
version = protocol.get_version(version_name)
|
19
|
+
version = protocol.add_version(version_name) if version.nil?
|
20
|
+
|
21
|
+
self.current_version_object = version
|
22
|
+
yield
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
8
26
|
# args is first specifying the name for variable to be displayed in JSON docs,
|
9
27
|
# and then the options. options are optional, if set, it can contains:
|
10
28
|
# - from
|
@@ -23,7 +41,14 @@ class Jsoning::ForDsl
|
|
23
41
|
end
|
24
42
|
end
|
25
43
|
|
26
|
-
|
44
|
+
# get the version instance
|
45
|
+
version_instance = if self.current_version_object.nil?
|
46
|
+
protocol.get_version!(:default)
|
47
|
+
else
|
48
|
+
current_version_object
|
49
|
+
end
|
50
|
+
|
51
|
+
mapper = Jsoning::Mapper.new(version_instance)
|
27
52
|
if block_given?
|
28
53
|
raise Jsoning::Error, "Cannot parse block to key"
|
29
54
|
else
|
@@ -38,6 +63,7 @@ class Jsoning::ForDsl
|
|
38
63
|
|
39
64
|
options.keys { |key| raise Jsoning::Error, "Undefined option: #{key}" }
|
40
65
|
|
41
|
-
|
66
|
+
# add mapper to the version
|
67
|
+
version_instance.add_mapper(mapper)
|
42
68
|
end
|
43
69
|
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# responsible of mapping from object to representable values, one field at a time
|
2
2
|
class Jsoning::Mapper
|
3
|
+
# access to a version instance
|
4
|
+
attr_accessor :version
|
5
|
+
|
3
6
|
# when mapped, what will be the mapped name
|
4
7
|
attr_writer :name
|
5
8
|
|
@@ -9,10 +12,11 @@ class Jsoning::Mapper
|
|
9
12
|
attr_accessor :parallel_variable
|
10
13
|
attr_accessor :custom_block
|
11
14
|
|
12
|
-
def initialize
|
15
|
+
def initialize(version_instance)
|
13
16
|
self.parallel_variable = nil
|
14
17
|
@default_value = nil
|
15
18
|
self.nullable = true
|
19
|
+
self.version = version_instance
|
16
20
|
end
|
17
21
|
|
18
22
|
def nullable?
|
@@ -30,29 +34,29 @@ class Jsoning::Mapper
|
|
30
34
|
end
|
31
35
|
|
32
36
|
# map this very specific object's field to target_hash
|
33
|
-
def extract(object, target_hash)
|
37
|
+
def extract(object, requested_version_name, target_hash)
|
34
38
|
target_value = nil
|
35
39
|
|
36
40
|
if object.respond_to?(parallel_variable)
|
37
41
|
parallel_val = object.send(parallel_variable)
|
38
|
-
target_value = deep_parse(parallel_val)
|
42
|
+
target_value = deep_parse(parallel_val, requested_version_name)
|
39
43
|
end
|
40
44
|
|
41
45
|
if target_value.nil?
|
42
|
-
target_value = self.default_value
|
46
|
+
target_value = self.default_value(requested_version_name)
|
43
47
|
if target_value.nil? && !self.nullable?
|
44
48
|
raise Jsoning::Error, "Null value given for '#{name}' when serializing #{object}"
|
45
49
|
end
|
46
50
|
end
|
47
|
-
target_hash[name] = deep_parse(target_value)
|
51
|
+
target_hash[name] = deep_parse(target_value, requested_version_name)
|
48
52
|
end
|
49
53
|
|
50
|
-
def default_value
|
54
|
+
def default_value(version_name = self.version.version_name)
|
51
55
|
if @default_value
|
52
56
|
if @default_value.is_a?(Proc)
|
53
|
-
return deep_parse(@default_value.())
|
57
|
+
return deep_parse(@default_value.(), version_name)
|
54
58
|
else
|
55
|
-
return deep_parse(@default_value)
|
59
|
+
return deep_parse(@default_value, version_name)
|
56
60
|
end
|
57
61
|
else
|
58
62
|
nil
|
@@ -60,7 +64,7 @@ class Jsoning::Mapper
|
|
60
64
|
end
|
61
65
|
|
62
66
|
private
|
63
|
-
def deep_parse(object)
|
67
|
+
def deep_parse(object, version_name)
|
64
68
|
parsed_data = nil
|
65
69
|
|
66
70
|
value_extractor = Jsoning::TYPE_EXTENSIONS[object.class.to_s]
|
@@ -70,19 +74,19 @@ class Jsoning::Mapper
|
|
70
74
|
if object.is_a?(Array)
|
71
75
|
parsed_data = []
|
72
76
|
object.each do |each_obj|
|
73
|
-
parsed_data << deep_parse(each_obj)
|
77
|
+
parsed_data << deep_parse(each_obj, version_name)
|
74
78
|
end
|
75
79
|
elsif object.is_a?(Hash)
|
76
80
|
parsed_data = {}
|
77
81
|
object.each do |obj_key_name, obj_val|
|
78
|
-
parsed_data[obj_key_name] = deep_parse(obj_val)
|
82
|
+
parsed_data[obj_key_name] = deep_parse(obj_val, version_name)
|
79
83
|
end
|
80
84
|
elsif object.is_a?(Integer) || object.is_a?(Float) || object.is_a?(String) ||
|
81
85
|
object.is_a?(TrueClass) || object.is_a?(FalseClass) || object.is_a?(NilClass)
|
82
86
|
parsed_data = object
|
83
87
|
else
|
84
88
|
protocol = Jsoning.protocol_for!(object.class)
|
85
|
-
parsed_data = protocol.retrieve_values_from(object)
|
89
|
+
parsed_data = protocol.retrieve_values_from(object, {version: version_name})
|
86
90
|
end
|
87
91
|
end
|
88
92
|
|
@@ -1,68 +1,77 @@
|
|
1
1
|
# takes care of the class
|
2
2
|
class Jsoning::Protocol
|
3
3
|
attr_reader :klass
|
4
|
-
attr_reader :
|
5
|
-
attr_reader :mappers_order
|
4
|
+
attr_reader :version_instances
|
6
5
|
|
7
6
|
def initialize(klass)
|
8
7
|
@klass = klass
|
8
|
+
@version_instances = {}
|
9
9
|
|
10
|
-
#
|
11
|
-
|
10
|
+
# each protocol has a default version named :default
|
11
|
+
add_version(:default)
|
12
12
|
|
13
|
-
|
13
|
+
self
|
14
14
|
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
# add a new version, a protocol can handle many version
|
17
|
+
# to export the JSON
|
18
|
+
def add_version(version_name)
|
19
|
+
unless version_name.is_a?(String) || version_name.is_a?(Symbol)
|
20
|
+
fail "Version name must be either a String or a Symbol"
|
21
|
+
end
|
22
|
+
version = Jsoning::Version.new(self)
|
23
|
+
version.version_name = version_name
|
24
|
+
@version_instances[version.version_name] = version
|
25
|
+
version
|
20
26
|
end
|
21
27
|
|
22
|
-
|
23
|
-
|
28
|
+
# retrieve a defined version, or return nil if undefined
|
29
|
+
def get_version(version_name)
|
30
|
+
@version_instances[version_name.to_s]
|
24
31
|
end
|
25
32
|
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
pretty = options["pretty"] if pretty.nil?
|
32
|
-
pretty = false if pretty.nil?
|
33
|
-
|
34
|
-
data = retrieve_values_from(object)
|
35
|
-
|
36
|
-
if pretty
|
37
|
-
JSON.pretty_generate(data)
|
38
|
-
else
|
39
|
-
JSON.generate(data)
|
40
|
-
end
|
33
|
+
# retrieve a defined version, or fail if undefined
|
34
|
+
def get_version!(version_name)
|
35
|
+
version = get_version(version_name)
|
36
|
+
fail Jsoning::Error, "Unknown version: #{version_name}" if version.nil?
|
37
|
+
version
|
41
38
|
end
|
42
39
|
|
43
40
|
# construct the JSON from given object
|
44
|
-
|
41
|
+
# options:
|
42
|
+
# - version: the version to be used for processing
|
43
|
+
def retrieve_values_from(object, options)
|
44
|
+
# user will pass in version, rather than version_name, although actually
|
45
|
+
# it is a version_name instead of version instance
|
46
|
+
version_name = options[:version]
|
47
|
+
# use default if version is undefined
|
48
|
+
version = get_version(version_name) || get_version(:default)
|
49
|
+
|
45
50
|
# hold data here
|
46
51
|
data = {}
|
47
52
|
|
48
|
-
mappers_order.each do |mapper_sym|
|
49
|
-
mapper = mapper_for(mapper_sym)
|
50
|
-
|
53
|
+
version.mappers_order.each do |mapper_sym|
|
54
|
+
mapper = version.mapper_for(mapper_sym)
|
55
|
+
# version_name and version may be different, that is when the user uses version
|
56
|
+
# that is not yet defined, therefore, will fall to default. therefore, user
|
57
|
+
# version_name as this is what is requested by the caller
|
58
|
+
mapper.extract(object, version_name, data)
|
51
59
|
end
|
52
60
|
|
53
61
|
data
|
54
62
|
end
|
55
63
|
|
56
64
|
# construct hash from given JSON
|
57
|
-
def construct_hash_from(json_string)
|
65
|
+
def construct_hash_from(json_string, version_name)
|
58
66
|
data = {}
|
59
67
|
|
60
68
|
# make all json obj keys to downcase, symbol
|
61
69
|
json_obj = JSON.parse(json_string)
|
62
70
|
json_obj = Hash[json_obj.map { |k, v| [k.to_s.downcase, v]}]
|
63
71
|
|
64
|
-
|
65
|
-
|
72
|
+
version = get_version!(version_name)
|
73
|
+
version.mappers_order.each do |mapper_sym|
|
74
|
+
mapper = version.mapper_for(mapper_sym)
|
66
75
|
mapper_key_name = mapper.name.to_s.downcase
|
67
76
|
|
68
77
|
mapper_default_value = mapper.default_value
|
@@ -79,9 +88,4 @@ class Jsoning::Protocol
|
|
79
88
|
|
80
89
|
data
|
81
90
|
end
|
82
|
-
|
83
|
-
private
|
84
|
-
def canonical_name(key_name)
|
85
|
-
key_name.to_s.downcase.to_sym
|
86
|
-
end
|
87
91
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# jsoning can output to conform to different versioning
|
2
|
+
class Jsoning::Version
|
3
|
+
attr_reader :version_name
|
4
|
+
|
5
|
+
# the protocol class
|
6
|
+
attr_reader :protocol
|
7
|
+
|
8
|
+
attr_reader :mappers
|
9
|
+
attr_reader :mappers_order
|
10
|
+
|
11
|
+
def initialize(protocol)
|
12
|
+
@protocol = protocol
|
13
|
+
# mappers, only storing symbol of mapped values
|
14
|
+
@mappers_order = []
|
15
|
+
@mappers = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def version_name=(version_name)
|
19
|
+
# version name is always be a string, because if user's version
|
20
|
+
# name happen to be an integer, we don't want to fail on that case
|
21
|
+
@version_name = version_name.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_mapper(mapper)
|
25
|
+
raise Jsoning::Error, "Mapper must be of class Jsoning::Mapper" unless mapper.is_a?(Jsoning::Mapper)
|
26
|
+
@mappers_order << canonical_mapper_name(mapper.name)
|
27
|
+
@mappers[canonical_mapper_name(mapper.name)] = mapper
|
28
|
+
end
|
29
|
+
|
30
|
+
def mapper_for(name)
|
31
|
+
@mappers[canonical_mapper_name(name)]
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def canonical_mapper_name(key_name)
|
36
|
+
key_name.to_s.downcase.to_sym
|
37
|
+
end
|
38
|
+
end
|
data/lib/jsoning/version.rb
CHANGED
data/lib/jsoning.rb
CHANGED
@@ -2,8 +2,9 @@ require "jsoning/version"
|
|
2
2
|
|
3
3
|
require "jsoning/dsl/for_dsl"
|
4
4
|
require "jsoning/exceptions/error"
|
5
|
-
require "jsoning/foundations/mapper"
|
6
5
|
require "jsoning/foundations/protocol"
|
6
|
+
require "jsoning/foundations/version"
|
7
|
+
require "jsoning/foundations/mapper"
|
7
8
|
|
8
9
|
require "json"
|
9
10
|
|
@@ -42,9 +43,43 @@ module Jsoning
|
|
42
43
|
end
|
43
44
|
|
44
45
|
# generate the json document
|
46
|
+
# options:
|
47
|
+
# - hash: specify if the return is a hash
|
48
|
+
# - pretty: only for when hash is set to flash, print JSON pretty
|
49
|
+
# - version: specify the version to be used for the processing
|
45
50
|
def generate(object, options = {})
|
51
|
+
Jsoning.initialize_type_extensions
|
46
52
|
protocol = protocol_for!(object.class)
|
47
|
-
|
53
|
+
|
54
|
+
# use default version if version is unspecified
|
55
|
+
options[:version] = :default if options[:version].nil?
|
56
|
+
|
57
|
+
if options[:hash] == true
|
58
|
+
return generate_hash(object, protocol, options)
|
59
|
+
else
|
60
|
+
return generate_json(object, protocol, options)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# generate a JSON object
|
65
|
+
# options:
|
66
|
+
# - pretty: pretty print json data
|
67
|
+
def generate_json(object, protocol, options)
|
68
|
+
pretty = options[:pretty]
|
69
|
+
pretty = options["pretty"] if pretty.nil?
|
70
|
+
pretty = false if pretty.nil?
|
71
|
+
|
72
|
+
data = protocol.retrieve_values_from(object, options)
|
73
|
+
|
74
|
+
if pretty
|
75
|
+
JSON.pretty_generate(data)
|
76
|
+
else
|
77
|
+
JSON.generate(data)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def generate_hash(object, protocol, options)
|
82
|
+
as_hash = protocol.retrieve_values_from(object, options)
|
48
83
|
end
|
49
84
|
|
50
85
|
@@type_extension_initialized = false
|
@@ -84,12 +119,13 @@ module Jsoning
|
|
84
119
|
end
|
85
120
|
end
|
86
121
|
|
122
|
+
# using [] will generate using default schema
|
87
123
|
def [](object)
|
88
|
-
|
89
|
-
protocol = protocol_for!(object.class)
|
90
|
-
protocol.retrieve_values_from(object)
|
124
|
+
generate(object, hash: true, version: :default)
|
91
125
|
end
|
92
126
|
|
127
|
+
# add custom type explaining to Jsoning how Jsoning should extract value from
|
128
|
+
# this kind of class
|
93
129
|
def add_type(klass, options = {})
|
94
130
|
processor = options[:processor]
|
95
131
|
raise Jsoning::Error, "Pass in processor that is a proc explaining how to extract the value" unless processor.is_a?(Proc)
|
@@ -102,15 +138,14 @@ module Jsoning
|
|
102
138
|
private
|
103
139
|
|
104
140
|
def Jsoning(object, options = {})
|
105
|
-
Jsoning.initialize_type_extensions
|
106
141
|
Jsoning.generate(object, options)
|
107
142
|
end
|
108
143
|
end
|
109
144
|
|
110
145
|
# parse the JSON String to Hash
|
111
|
-
def parse(json_string, klass)
|
146
|
+
def parse(json_string, klass, version_name = :default)
|
112
147
|
Jsoning.initialize_type_extensions
|
113
148
|
protocol = protocol_for!(klass)
|
114
|
-
protocol.construct_hash_from(json_string)
|
149
|
+
protocol.construct_hash_from(json_string, version_name)
|
115
150
|
end
|
116
151
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsoning
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Pahlevi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -78,6 +78,7 @@ files:
|
|
78
78
|
- lib/jsoning/exceptions/error.rb
|
79
79
|
- lib/jsoning/foundations/mapper.rb
|
80
80
|
- lib/jsoning/foundations/protocol.rb
|
81
|
+
- lib/jsoning/foundations/version.rb
|
81
82
|
- lib/jsoning/version.rb
|
82
83
|
homepage: http://github.com/saveav/jsoning
|
83
84
|
licenses:
|