json_convertible 0.0.1

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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/json_convertible.rb +250 -0
  3. metadata +141 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c3e54292e01cbe5d9ccdcfc1af48aabd142500b2e076eb8ff44fd9ccde51119c
4
+ data.tar.gz: 51f005e040de492a4bb071a8f5b0ccb72b8d4ec3463b8cdf466ce6f66d4145fb
5
+ SHA512:
6
+ metadata.gz: 7156554c12f21c0aa2b0dbe427188bf0f83b82ee9e8c4b3f1f6f7a98c47152300a5588cc71e27ed3dcc4c38a19f11f92f1da441499de017a91be6347a1e96436
7
+ data.tar.gz: ce8eaf1471ad3be846ccf81183868f48a064e8d9eee99d1fa5f0b87ca26fb7e9153fed7ba8fc46c4503c17a864bbd5edb7535692cd72c084462b8dca7c03e0e5
@@ -0,0 +1,250 @@
1
+ require "json"
2
+
3
+ # Mixin for encoding/decoding JSON data classes.
4
+ module JSONConvertible
5
+
6
+ class JSONConvertibleError < StandardError
7
+ end
8
+
9
+ def self.included(base)
10
+ base.send(:include, InstanceMethods)
11
+ base.extend(ClassMethods)
12
+ end
13
+
14
+ # add these as instance methods
15
+ module InstanceMethods
16
+ def to_json(options = {})
17
+ to_object_dictionary.to_json(options)
18
+ end
19
+
20
+ # @ignore_instance_variables: optional to provide a list of
21
+ # variables to attributes to ignore
22
+ # @example
23
+ #
24
+ # ignore_instance_variables: [:@project, :@something_else]
25
+ #
26
+ def to_object_dictionary(ignore_instance_variables: [])
27
+ object_hash = {}
28
+ instance_variables.each do |var|
29
+ next if ignore_instance_variables.include?(var)
30
+
31
+ # If we encounter with a `var` which value is an `Array`, we should iterate
32
+ # over its value and use its own `to_object_dictionary`.
33
+ if instance_variable_get(var).kind_of?(Array)
34
+ object_array = []
35
+ instance_variable_get(var).each do |obj|
36
+ # If the `Array` type does not include the JSONConvertible mixin, don't
37
+ # call `to_object_dictionary` on the elements, since the method does not exist
38
+ if obj.class.include?(JSONConvertible)
39
+ object_array << obj.to_object_dictionary
40
+ else
41
+ object_array << obj
42
+ end
43
+ end
44
+ # In this step we have all the objects, lastly we need the key of the array.
45
+ var_name, = _to_object_dictionary(var)
46
+ object_hash[var_name] = object_array
47
+ else
48
+ var_name, instance_variable_value = _to_object_dictionary(var)
49
+ object_hash[var_name] = instance_variable_value
50
+ end
51
+ end
52
+ return object_hash
53
+ end
54
+
55
+ protected
56
+
57
+ def _to_object_dictionary(var)
58
+ # For a given object variable we check if it includes some custom value mapping to JSON.
59
+ if self.class.attribute_name_to_json_proc_map.key?(var)
60
+ instance_variable_value = self.class.attribute_name_to_json_proc_map[var].call(instance_variable_get(var))
61
+ else
62
+ instance_variable_value = instance_variable_get(var)
63
+ end
64
+ # For a given object variable we check if it includes some custom property mapping to JSON
65
+ if self.class.attribute_key_name_map.key?(var)
66
+ var_name = self.class.attribute_key_name_map[var]
67
+ else
68
+ var_name = var.to_s[1..-1]
69
+ end
70
+ return var_name, instance_variable_value
71
+ end
72
+ end
73
+
74
+ # add these as class methods
75
+ module ClassMethods
76
+ def from_json!(json_object)
77
+ instance, json_object = _initialize_using!(json_object)
78
+ json_object.each do |var, val|
79
+ # If we encounter with a value that is represented by an array, iterate over it.
80
+ if val.kind_of?(Array)
81
+ # For each of the objects in the array, we call the protected method to build the object array.
82
+ array_property = []
83
+ array_name = nil
84
+ val.each do |array_val|
85
+ array_name, this_instance = _from_json!(var, array_val, is_iterable: true)
86
+ array_property << this_instance
87
+ end
88
+ if array_name.nil?
89
+ if attribute_key_name_map.key(var)
90
+ array_name = attribute_key_name_map.key(var).to_sym
91
+ else
92
+ array_name = "@#{var}".to_sym
93
+ end
94
+ end
95
+ instance.instance_variable_set(array_name, array_property)
96
+ else
97
+ var_name, var_value = _from_json!(var, val)
98
+ instance.instance_variable_set(var_name, var_value)
99
+ end
100
+ end
101
+ return instance
102
+ end
103
+
104
+ # class method
105
+ # This method is intended to be overridden by any
106
+ # class that implements `JSONConvertible` and need
107
+ # to use a custom mapping of the attributes to JSON keys.
108
+ #
109
+ # @example
110
+ #
111
+ # def self.attribute_key_name_map
112
+ # return { :@some_key => "some_key_in_json" }
113
+ # end
114
+ #
115
+ # @return [Hash] of mapping properties to keys in the JSON
116
+ def attribute_key_name_map
117
+ return {}
118
+ end
119
+
120
+ # class method
121
+ # This method is intended to be overridden by any
122
+ # class that implements `JSONConvertible` and need
123
+ # to encode the result of the class attributes in a
124
+ # certain format into the JSON.
125
+ #
126
+ # @example
127
+ #
128
+ # def self.attribute_name_to_json_proc_map
129
+ # timestamp_to_json_proc = proc { |timestamp|
130
+ # timestamp.strftime('%Q')
131
+ # }
132
+ # return { :@timestamp => timestamp_to_json_proc }
133
+ # end
134
+ #
135
+ # @return [Hash] of properties and procs formatting to JSON
136
+ def attribute_name_to_json_proc_map
137
+ return {}
138
+ end
139
+
140
+ # class method
141
+ # This method is intended to be overridden by any
142
+ # class that implements `JSONConvertible` and need
143
+ # to decode the JSON values back to the original types
144
+ # of the class attributes.
145
+ #
146
+ # @example
147
+ #
148
+ # def self.json_to_attribute_name_proc_map
149
+ # json_to_timestamp_proc = proc { |json|
150
+ # Time.at(json.to_i)
151
+ # }
152
+ # return { :@timestamp => json_to_timestamp_proc }
153
+ # end
154
+ #
155
+ # @return [Hash] of properties and procs formatting from JSON
156
+ def json_to_attribute_name_proc_map
157
+ return {}
158
+ end
159
+
160
+ # class method
161
+ # This method is intended to be overridden by any
162
+ # class that implements `JSONConvertible` and need
163
+ # to provide the encoder information about which types
164
+ # are each attribute of the class.
165
+ #
166
+ # @example
167
+ #
168
+ #
169
+ # def attribute_to_type_map
170
+ # return { :@string_attribute => String, :@custom_class_attribute => CustomClass }
171
+ # end
172
+ #
173
+ # @return [Hash] of properties and their types
174
+ def attribute_to_type_map
175
+ return {}
176
+ end
177
+
178
+ # class method
179
+ # This method is intended to be overridden by any
180
+ # class that implements `JSONConvertible` and need
181
+ # to provide a custom mapping for a enumerable property
182
+ # in the JSON.
183
+ #
184
+ # @param enumerable_property_name [Any] the property name of the object.
185
+ # @param current_json_object [Hash] the hash object of `enumerable_property_name` for a given iteration step.
186
+ #
187
+ # @example
188
+ #
189
+ # def map_enumerable_type(enumerable_property_name: nil, current_json_object: nil)
190
+ # if enumerable_property_name == :@job_triggers
191
+ # JobTrigger needs a factory method that reads `json[:type]` and instantiates the proper type
192
+ # return FastlaneCI::JobTrigger.create(json: current_json_object)
193
+ # end
194
+ # end
195
+ # @return [Any] object in the array by the given `property_name` and `json_object`.
196
+ def map_enumerable_type(enumerable_property_name: nil, current_json_object: nil)
197
+ return nil
198
+ end
199
+
200
+ protected
201
+
202
+ def _from_json!(var, val, is_iterable: false)
203
+ if attribute_key_name_map.key(var)
204
+ var_name = attribute_key_name_map.key(var).to_sym
205
+ else
206
+ var_name = "@#{var}".to_sym
207
+ end
208
+
209
+ if json_to_attribute_name_proc_map.key?(var_name)
210
+ var_value = json_to_attribute_name_proc_map[var_name].call(val)
211
+ else
212
+ var_value = val
213
+ end
214
+
215
+ if attribute_to_type_map.key?(var_name)
216
+ if attribute_to_type_map[var_name].include?(JSONConvertible)
217
+ # classes that include `JSONConvertible` take precedence over custom mapping.
218
+ var_value = attribute_to_type_map[var_name].from_json!(val)
219
+ else
220
+ raise TypeError, "#{var_name} does not implement `FastlaneCI::JSONConvertible`"
221
+ end
222
+ elsif is_iterable
223
+ # This is only intended for array properties, it passes the final variable name and a single object of
224
+ # the variable array. Expects to return and object.
225
+ var_value = map_enumerable_type(enumerable_property_name: var_name, current_json_object: val)
226
+ end
227
+
228
+ return var_name, var_value
229
+ end
230
+
231
+ def _initialize_using!(json_object)
232
+ instance = allocate
233
+ required_init_params = instance.method(:initialize).parameters
234
+ .select { |arg| arg[0] == :keyreq }
235
+ .map(&:last)
236
+ unless (required_init_params - json_object.keys).empty?
237
+ raise JSONConvertibleError.new, "Required initialization parameters not found in the object: #{json_object}"
238
+ end
239
+
240
+ init_params_hash = json_object.select { |key, _value| required_init_params.include?(key) }
241
+ if instance.method(:initialize).parameters.empty?
242
+ instance.send(:initialize)
243
+ else
244
+ instance.send(:initialize, init_params_hash)
245
+ end
246
+ clean_json_object = json_object.reject { |key| required_init_params.include?(key) }
247
+ return instance, clean_json_object
248
+ end
249
+ end
250
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: json_convertible
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jorge Revuelta H
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-05-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "<"
18
+ - !ruby/object:Gem::Version
19
+ version: 3.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "<"
25
+ - !ruby/object:Gem::Version
26
+ version: 3.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: coveralls
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.10'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.10'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 1.12.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 1.12.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-performance
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: A lightweight Ruby mixin for encoding/decoding JSON data classes.
112
+ email: minuscorp@gmail.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - "./lib/json_convertible.rb"
118
+ homepage: https://rubygems.org/gems/json_convertible
119
+ licenses:
120
+ - Apache-2.0
121
+ metadata: {}
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: 2.4.0
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubygems_version: 3.0.1
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: A lightweight Ruby mixin for encoding/decoding JSON data classes.
141
+ test_files: []