jsoning 0.5.0 → 0.6.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
  SHA1:
3
- metadata.gz: 3fd493cccd6b9c1cfb0d8db8dd60cd9a6b61e8b9
4
- data.tar.gz: 90afc5cd5c89dd9ae57ff0eb68059454aecfec28
3
+ metadata.gz: 69a10f48340146f1c819bd5d038088913fdd63e8
4
+ data.tar.gz: d5761009f4937cf74743679b48ffdd471bfbf3dc
5
5
  SHA512:
6
- metadata.gz: 979a26bc4aadde42006c99ed4b1b137e36c1c24a1efe6f400cca71708f695ac37395630c84ebbb4bb412a28245fc1056f34b377503ad4ea04da44aa3ab4c64db
7
- data.tar.gz: 48e6a895798ecd4d255f632c669d366d26dca4d954f3dcfd1906e45da69783de43ca0f7c59e0dd0a61316d94fa253dbfb5f32f3337c45ef2f709d943806dd60d
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
  [![Code Climate](https://codeclimate.com/github/saveav/jsoning/badges/gpa.svg)](https://codeclimate.com/github/saveav/jsoning)
8
8
  [ ![Codeship Status for saveav/bali](https://codeship.com/projects/b58d3950-493b-0133-a217-168d58eb1296/status?branch=release)](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).
@@ -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
- mapper = Jsoning::Mapper.new
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
- protocol.add_mapper mapper
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 :mappers
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
- # mappers, only storing symbol of mapped values
11
- @mappers_order = []
10
+ # each protocol has a default version named :default
11
+ add_version(:default)
12
12
 
13
- @mappers = {}
13
+ self
14
14
  end
15
15
 
16
- def add_mapper(mapper)
17
- raise Jsoning::Error, "Mapper must be of class Jsoning::Mapper" unless mapper.is_a?(Jsoning::Mapper)
18
- @mappers_order << canonical_name(mapper.name)
19
- @mappers[canonical_name(mapper.name)] = mapper
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
- def mapper_for(name)
23
- @mappers[canonical_name(name)]
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
- # generate a JSON object
27
- # options:
28
- # - pretty: pretty print json data
29
- def generate(object, options = {})
30
- pretty = options[:pretty]
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
- def retrieve_values_from(object)
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
- mapper.extract(object, data)
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
- mappers_order.each do |mapper_sym|
65
- mapper = mapper_for(mapper_sym)
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
@@ -1,3 +1,3 @@
1
1
  module Jsoning
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
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
- protocol.generate(object, options)
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
- Jsoning.initialize_type_extensions
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.5.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-03 00:00:00.000000000 Z
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: