tidy_json 0.2.1 → 0.2.2
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/Gemfile +2 -0
- data/README.md +51 -39
- data/Rakefile +2 -0
- data/lib/tidy_json.rb +175 -98
- data/lib/tidy_json/dedication.rb +10 -8
- data/lib/tidy_json/version.rb +1 -1
- data/test/test_tidy_json.rb +73 -15
- data/tidy_json.gemspec +5 -3
- metadata +19 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e53774339fb56a01ae47f0dd087159e9a1d2fa070dccda937d5a7de399e07ba9
|
4
|
+
data.tar.gz: 9f6736f1c5ef84b2d4e89b507b7fa417b3ab713b01226c55c86afc54d07699ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f35fe6a646a7e47c5926e04a10222cd483f45d177d3808d3de51040404b6d9ab4fdee940ceb077dba5e74b55ca171699331896485091e6c0c1a0f7c9f1ecb422
|
7
|
+
data.tar.gz: d599497adc2efb538341d2b4ad09b9b7b9f47ffd30ee056f9c47ceeda42953a6a23eb8266aa87c7ed220424a4a68ec323c864f10e7a78618bd1688dd4e4d627e
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# tidy_json
|
2
2
|
|
3
|
-
[![Build Status][travis_build_status_badge]][travis_build_status] [![cci_build_status_badge]][cci_build_status] ![Gem Version][gem_version_badge]
|
3
|
+
[![Build Status][travis_build_status_badge]][travis_build_status] [![cci_build_status_badge]][cci_build_status] [![Gem Version][gem_version_badge]][gem_version]
|
4
4
|
|
5
5
|
A mixin providing (recursive) JSON serialization and pretty printing.
|
6
6
|
|
@@ -27,47 +27,58 @@ require 'tidy_json'
|
|
27
27
|
class Jsonable
|
28
28
|
attr_reader :a, :b
|
29
29
|
def initialize
|
30
|
-
@a = { a: 'uno',
|
31
|
-
@b = {
|
30
|
+
@a = { a: 'uno', f: ['I', 'II', 'III', ['i.', 'ii.', 'iii.', { 'ichi': "\u{4e00}", 'ni': "\u{4e8c}", 'san': "\u{4e09}", 'yon': "\u{56db}" }]], b: 'dos' }
|
31
|
+
@b = { z: { iv: 4, ii: 'duos', iii: 3, i: 'one' }, b: ['two', 3, '<abbr title="four">IV</abbr>'], a: 1, f: %w[x y z] }
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
35
|
my_jsonable = Jsonable.new
|
36
36
|
|
37
37
|
JSON.parse my_jsonable.stringify
|
38
|
-
# => {"class"=>"Jsonable", "a"=>{"a"=>"uno", "
|
38
|
+
# => {"class"=>"Jsonable", "a"=>{"a"=>"uno", "f"=>["I", "II", "III", ["i.", "ii.", "iii.", {"ichi"=>"一", "ni"=>"二", "san"=>"三", "yon"=>"四"}]], "b"=>"dos"}, "b"=>{"z"=>{"iv"=>4, "ii"=>"duos", "iii"=>3, "i"=>"one"}, "b"=>["two", 3, "<abbr title=\"four\">IV</abbr>"], "a"=>1, "f"=>["x", "y", "z"]}}
|
39
39
|
|
40
|
-
puts my_jsonable.to_tidy_json(indent: 4)
|
40
|
+
puts my_jsonable.to_tidy_json(indent: 4, sort: true)
|
41
41
|
# {
|
42
|
-
# "class": "Jsonable",
|
43
42
|
# "a": {
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
43
|
+
# "a": "uno",
|
44
|
+
# "b": "dos",
|
45
|
+
# "f": [
|
46
|
+
# "I",
|
47
|
+
# "II",
|
48
|
+
# "III",
|
49
|
+
# [
|
50
|
+
# "i.",
|
51
|
+
# "ii.",
|
52
|
+
# "iii.",
|
53
|
+
# {
|
54
|
+
# "ichi": "一",
|
55
|
+
# "ni": "二",
|
56
|
+
# "san": "三",
|
57
|
+
# "yon": "四"
|
58
|
+
# }
|
59
|
+
# ]
|
60
|
+
# ]
|
61
|
+
# },
|
62
|
+
# "b": {
|
63
|
+
# "a": 1,
|
64
|
+
# "b": [
|
65
|
+
# "two",
|
66
|
+
# 3,
|
67
|
+
# "<abbr title=\"four\">IV</abbr>"
|
68
|
+
# ],
|
69
|
+
# "f": [
|
70
|
+
# "x",
|
71
|
+
# "y",
|
72
|
+
# "z"
|
73
|
+
# ],
|
74
|
+
# "z": {
|
75
|
+
# "i": "one",
|
76
|
+
# "ii": "duos",
|
77
|
+
# "iii": 3,
|
78
|
+
# "iv": 4
|
79
|
+
# }
|
80
|
+
# },
|
81
|
+
# "class": "Jsonable"
|
71
82
|
# }
|
72
83
|
# => nil
|
73
84
|
```
|
@@ -78,15 +89,16 @@ puts my_jsonable.to_tidy_json(indent: 4)
|
|
78
89
|
- [json](https://rubygems.org/gems/json) ~> 2.2
|
79
90
|
|
80
91
|
#### Building
|
81
|
-
- [
|
92
|
+
- [test-unit](https://rubygems.org/gems/test-unit) ~> 3.3
|
82
93
|
- [yard](https://rubygems.org/gems/yard) ~> 0.9
|
83
94
|
|
84
95
|
### License
|
85
|
-
[MIT](https://
|
96
|
+
[MIT](https://github.com/rdipardo/tidy_json/blob/master/LICENSE)
|
86
97
|
|
87
98
|
|
88
99
|
[travis_build_status]: https://travis-ci.com/rdipardo/tidy_json
|
89
|
-
[cci_build_status]: https://circleci.com/gh/rdipardo/tidy_json
|
100
|
+
[cci_build_status]: https://circleci.com/gh/rdipardo/tidy_json/tree/master
|
90
101
|
[cci_build_status_badge]: https://circleci.com/gh/rdipardo/tidy_json.svg?style=svg
|
91
|
-
[travis_build_status_badge]: https://travis-ci.com/rdipardo/tidy_json.svg
|
92
|
-
[
|
102
|
+
[travis_build_status_badge]: https://travis-ci.com/rdipardo/tidy_json.svg?branch=master
|
103
|
+
[gem_version]: https://badge.fury.io/rb/tidy_json
|
104
|
+
[gem_version_badge]: https://badge.fury.io/rb/tidy_json.svg
|
data/Rakefile
CHANGED
data/lib/tidy_json.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
require_relative 'tidy_json/version'
|
3
5
|
|
@@ -9,11 +11,12 @@ module TidyJson
|
|
9
11
|
# Emits a pretty-printed JSON representation of the given +obj+.
|
10
12
|
#
|
11
13
|
# @param obj [Object] A Ruby object that can be parsed as JSON.
|
12
|
-
# @param opts [Hash]
|
13
|
-
#
|
14
|
+
# @param opts [Hash] Output format options.
|
15
|
+
# @option (see Formatter#initialize)
|
14
16
|
# @return [String] A pretty-printed JSON string.
|
15
17
|
def self.tidy(obj = {}, opts = {})
|
16
18
|
formatter = Formatter.new(opts)
|
19
|
+
obj = sort_keys(obj) if formatter.sorted
|
17
20
|
str = ''
|
18
21
|
|
19
22
|
if obj.instance_of?(Hash)
|
@@ -37,14 +40,64 @@ module TidyJson
|
|
37
40
|
str << "]\n"
|
38
41
|
end
|
39
42
|
|
43
|
+
if (extra_comma = /(?<trail>,\s*[\]\}])$/.match(str))
|
44
|
+
str = str.sub(extra_comma[:trail],
|
45
|
+
extra_comma[:trail].slice(1, str.length.pred))
|
46
|
+
end
|
47
|
+
|
40
48
|
str
|
41
49
|
end
|
42
50
|
|
51
|
+
##
|
52
|
+
# Returns the given +obj+ with keys in ascending order to a maximum depth of
|
53
|
+
# 2.
|
54
|
+
#
|
55
|
+
# @param obj [Hash, Array<Hash>] A dictionary-like object or collection
|
56
|
+
# thereof.
|
57
|
+
# @return [Hash, Array<Hash>, Object] A copy of the given +obj+ with top- and
|
58
|
+
# second-level keys in ascending order, or else an identical copy of +obj+.
|
59
|
+
# @note +obj+ is returned unchanged if: 1) it's not iterable; 2) it's an
|
60
|
+
# empty collection; 3) any one of its elements is not hashable (and +obj+
|
61
|
+
# is an array).
|
62
|
+
def self.sort_keys(obj = {})
|
63
|
+
return obj if !obj.respond_to?(:each) || obj.empty? ||
|
64
|
+
(obj.instance_of?(Array) &&
|
65
|
+
!obj.all? { |e| e.respond_to? :keys })
|
66
|
+
|
67
|
+
sorted = {}
|
68
|
+
sorter = lambda { |data, ret_val|
|
69
|
+
data.keys.sort.each do |k|
|
70
|
+
ret_val[k.to_sym] = if data[k].instance_of? Hash
|
71
|
+
sorter.call(data[k], {})
|
72
|
+
else
|
73
|
+
data[k]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
return ret_val
|
78
|
+
}
|
79
|
+
|
80
|
+
if obj.instance_of? Array
|
81
|
+
temp = {}
|
82
|
+
sorted = []
|
83
|
+
|
84
|
+
(obj.sort_by { |h| h.keys.first }).each_with_index do |h, idx|
|
85
|
+
temp[idx] = sorter.call(h, {})
|
86
|
+
end
|
87
|
+
|
88
|
+
temp.keys.each { |k| sorted << temp[k] }
|
89
|
+
else
|
90
|
+
sorted = sorter.call(obj, {})
|
91
|
+
end
|
92
|
+
|
93
|
+
sorted
|
94
|
+
end
|
95
|
+
|
43
96
|
##
|
44
97
|
# Like +TidyJson::tidy+, but callable by the sender object.
|
45
98
|
#
|
46
|
-
# @param opts [Hash]
|
47
|
-
#
|
99
|
+
# @param opts [Hash] Output format options.
|
100
|
+
# @option (see Formatter#initialize)
|
48
101
|
# @return [String] A pretty-printed JSON string.
|
49
102
|
def to_tidy_json(opts = {})
|
50
103
|
if !instance_variables.empty?
|
@@ -71,14 +124,17 @@ module TidyJson
|
|
71
124
|
end
|
72
125
|
|
73
126
|
##
|
74
|
-
# Writes a JSON representation of the sender object to the file specified by
|
127
|
+
# Writes a JSON representation of the sender object to the file specified by
|
128
|
+
# +out+.
|
75
129
|
#
|
76
130
|
# @param out [String] The destination filename.
|
77
|
-
# @param opts [Hash]
|
78
|
-
#
|
79
|
-
#
|
131
|
+
# @param opts [Hash] Output format options.
|
132
|
+
# @option (see Formatter#initialize)
|
133
|
+
# @option opts [Boolean] :tidy (false) Whether or not the output should be
|
134
|
+
# pretty-printed.
|
80
135
|
# @return [String, nil] The path to the written output file, if successful.
|
81
|
-
def write_json(out = "#{self.class.name}_#{Time.now.to_i}",
|
136
|
+
def write_json(out = "#{self.class.name}_#{Time.now.to_i}",
|
137
|
+
opts = { tidy: false })
|
82
138
|
path = nil
|
83
139
|
|
84
140
|
File.open("#{out}.json", 'w') do |f|
|
@@ -91,10 +147,10 @@ module TidyJson
|
|
91
147
|
if opts[:tidy] then to_tidy_json(opts)
|
92
148
|
else to_json
|
93
149
|
end
|
94
|
-
|
150
|
+
end
|
95
151
|
end
|
96
152
|
|
97
|
-
path
|
153
|
+
path&.path
|
98
154
|
rescue IOError, RuntimeError, NoMethodError => e
|
99
155
|
warn "#{__FILE__}.#{__LINE__}: #{e.message}"
|
100
156
|
end
|
@@ -105,8 +161,8 @@ module TidyJson
|
|
105
161
|
# @api private
|
106
162
|
class Serializer
|
107
163
|
##
|
108
|
-
# Searches +obj+ to a
|
109
|
-
#
|
164
|
+
# Searches +obj+ to a maximum depth of 2 for readable attributes, storing
|
165
|
+
# them as key-value pairs in +json_hash+.
|
110
166
|
#
|
111
167
|
# @param obj [Object] A Ruby object that can be parsed as JSON.
|
112
168
|
# @param json_hash [{String,Symbol => #to_s}] Accumulator.
|
@@ -130,7 +186,7 @@ module TidyJson
|
|
130
186
|
nested = nil
|
131
187
|
|
132
188
|
val.each.any? do |k, v|
|
133
|
-
|
189
|
+
unless v.instance_variables.empty?
|
134
190
|
nested_key = k
|
135
191
|
nested = v
|
136
192
|
end
|
@@ -141,7 +197,8 @@ module TidyJson
|
|
141
197
|
if nested
|
142
198
|
pos = val.keys.select { |k| k === nested_key }.first.to_sym
|
143
199
|
nested.instance_variables.each do
|
144
|
-
json_hash[key][pos] = serialize(nested,
|
200
|
+
json_hash[key][pos] = serialize(nested,
|
201
|
+
class: nested.class.name)
|
145
202
|
end
|
146
203
|
end
|
147
204
|
|
@@ -152,50 +209,52 @@ module TidyJson
|
|
152
209
|
val.each do |elem|
|
153
210
|
i = val.index(elem)
|
154
211
|
|
155
|
-
# multi-dimensional array
|
212
|
+
# member is a multi-dimensional array
|
156
213
|
if elem.instance_of?(Array)
|
157
214
|
nested = []
|
158
215
|
elem.each do |e|
|
159
216
|
j = elem.index(e)
|
160
217
|
|
161
218
|
# nested array element is a class object
|
162
|
-
if e.instance_variables.
|
219
|
+
if !e.instance_variables.empty?
|
163
220
|
json_hash[key][j] = { class: e.class.name }
|
164
221
|
|
165
222
|
# recur over the contained object
|
166
223
|
serialize(e, json_hash[key][j])
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
224
|
+
|
225
|
+
# some kind of collection?
|
226
|
+
elsif e.respond_to?(:each)
|
227
|
+
temp = []
|
228
|
+
e.each { |el| temp << el }
|
229
|
+
nested << temp
|
230
|
+
|
231
|
+
# primitive type
|
232
|
+
else nested << e
|
175
233
|
end
|
176
234
|
end
|
177
235
|
# ~iteration of nested array elements
|
178
236
|
|
179
237
|
json_hash[key] << nested
|
180
238
|
|
239
|
+
# member is a flat array
|
181
240
|
else
|
182
|
-
#
|
183
|
-
if elem.instance_variables.
|
241
|
+
# class object?
|
242
|
+
if !elem.instance_variables.empty?
|
184
243
|
json_hash[key] << { class: elem.class.name }
|
185
244
|
serialize(elem, json_hash[key][i])
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
245
|
+
|
246
|
+
# leverage 1:1 mapping of Hash:object
|
247
|
+
elsif elem.instance_of?(Hash)
|
248
|
+
json_hash[key] = val
|
249
|
+
|
250
|
+
# some kind of collection
|
251
|
+
elsif elem.respond_to?(:each)
|
252
|
+
temp = []
|
253
|
+
elem.each { |e| temp << e }
|
254
|
+
json_hash[key] << temp
|
255
|
+
|
256
|
+
# primitive type
|
257
|
+
else json_hash[key] << elem
|
199
258
|
end
|
200
259
|
end
|
201
260
|
end
|
@@ -204,7 +263,7 @@ module TidyJson
|
|
204
263
|
# process any nested class members, i.e., handle a recursive call
|
205
264
|
# to Serializer.serialize
|
206
265
|
elsif obj.index(val) || json_hash.key?(key)
|
207
|
-
if val.instance_variables.
|
266
|
+
if !val.instance_variables.empty?
|
208
267
|
class_elem = { class: val.class.name }
|
209
268
|
json_hash[key] << class_elem
|
210
269
|
k = json_hash[key].index(class_elem)
|
@@ -215,20 +274,20 @@ module TidyJson
|
|
215
274
|
|
216
275
|
# process uncollected class members
|
217
276
|
else
|
218
|
-
# member a class object
|
219
|
-
if val.instance_variables.
|
277
|
+
# member is a class object
|
278
|
+
if !val.instance_variables.empty?
|
220
279
|
json_hash[key] = { class: val.class.name }
|
221
280
|
serialize(val, json_hash[key])
|
222
|
-
else
|
223
|
-
# member a hash element
|
224
|
-
if json_hash.key?(key) && \
|
225
|
-
!json_hash[key].has_val?(val) && \
|
226
|
-
json_hash[key].instance_of?(Hash)
|
227
281
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
282
|
+
# member belongs to a contained object
|
283
|
+
elsif json_hash.key?(key) &&
|
284
|
+
!json_hash[key].has_val?(val) &&
|
285
|
+
json_hash[key].instance_of?(Hash)
|
286
|
+
|
287
|
+
json_hash[key][key] = val
|
288
|
+
|
289
|
+
# primitive member
|
290
|
+
else json_hash[key] = val
|
232
291
|
end
|
233
292
|
end
|
234
293
|
rescue NoMethodError
|
@@ -249,47 +308,64 @@ module TidyJson
|
|
249
308
|
#
|
250
309
|
# @api private
|
251
310
|
class Formatter
|
252
|
-
attr_reader :indent
|
253
|
-
|
311
|
+
attr_reader :indent, :sorted
|
254
312
|
# @!attribute indent
|
255
|
-
#
|
313
|
+
# @return [String] the string of white space used by this +Formatter+ to
|
314
|
+
# indent object members.
|
315
|
+
|
316
|
+
# @!attribute sorted
|
317
|
+
# @return [Boolean] whether or not this +Formatter+ will sort object
|
318
|
+
# members by key name.
|
256
319
|
|
257
320
|
##
|
258
|
-
#
|
259
|
-
# @
|
260
|
-
#
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
#
|
321
|
+
# @param opts [Hash] Formatting options.
|
322
|
+
# @option opts [[2,4,6,8,10,12]] :indent (2) An even number of white spaces
|
323
|
+
# to indent each object member.
|
324
|
+
# @option opts [Boolean] :sort (false) Whether or not object members should
|
325
|
+
# be sorted by key.
|
326
|
+
def initialize(opts = {})
|
327
|
+
# The number of times to reduce the left indent of a nested array's
|
328
|
+
# opening bracket
|
265
329
|
@left_bracket_offset = 0
|
266
330
|
|
267
|
-
##
|
268
331
|
# True if printing a nested array
|
269
332
|
@need_offset = false
|
270
333
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
@
|
334
|
+
# don't test for the more explicit :integer? method because it's defined
|
335
|
+
# for floating point numbers also
|
336
|
+
valid_width = opts[:indent].positive? \
|
337
|
+
if opts[:indent].respond_to?(:times) &&
|
338
|
+
(2..12).step(2).include?(opts[:indent])
|
339
|
+
@indent = "\s" * (valid_width ? opts[:indent] : 2)
|
340
|
+
@sorted = opts[:sort] || false
|
278
341
|
end
|
342
|
+
# ~Formatter#initialize
|
279
343
|
|
280
344
|
##
|
281
345
|
# Returns the given +node+ as pretty-printed JSON.
|
282
346
|
#
|
283
347
|
# @param node [#to_s] A visible attribute of +obj+.
|
284
|
-
# @param obj [{Object =>
|
348
|
+
# @param obj [{Object => #to_s}, <#to_s>] The enumerable object
|
349
|
+
# containing +node+.
|
285
350
|
# @return [String] A formatted string representation of +node+.
|
286
351
|
def format_node(node, obj)
|
287
352
|
str = ''
|
288
353
|
indent = @indent
|
289
354
|
|
355
|
+
# BUG: arrays containing repeated elements may produce a trailing comma
|
356
|
+
# since Array#index returns the first occurance; in this case the last
|
357
|
+
# element can't be detected by index; a temporary hack in TidyJson::tidy
|
358
|
+
# attempts to correct for this
|
359
|
+
is_last = (obj.length <= 1) ||
|
360
|
+
(obj.length > 1 &&
|
361
|
+
(obj.instance_of?(Hash) &&
|
362
|
+
(obj.key(obj.values.last) === obj.key(node))) ||
|
363
|
+
(obj.instance_of?(Array) && obj.size.pred == obj.index(node)))
|
364
|
+
|
290
365
|
if node.instance_of?(Array)
|
291
366
|
str << "[\n"
|
292
367
|
|
368
|
+
# format array elements
|
293
369
|
node.each do |elem|
|
294
370
|
if elem.instance_of?(Hash)
|
295
371
|
str << "#{(indent * 2)}{\n"
|
@@ -297,23 +373,25 @@ module TidyJson
|
|
297
373
|
elem.each_with_index do |inner_h, h_idx|
|
298
374
|
str << "#{(indent * 3)}\"#{inner_h.first}\": "
|
299
375
|
str << node_to_str(inner_h.last, 4)
|
300
|
-
str << ', ' unless h_idx ==
|
376
|
+
str << ', ' unless h_idx == elem.to_a.length.pred
|
301
377
|
str << "\n"
|
302
378
|
end
|
303
379
|
|
304
380
|
str << "#{(indent * 2)}}"
|
305
|
-
str << ',' unless node.index(elem) ==
|
306
|
-
str << "\n" unless node.index(elem) ==
|
381
|
+
str << ',' unless node.index(elem) == node.length.pred
|
382
|
+
str << "\n" unless node.index(elem) == node.length.pred
|
307
383
|
|
384
|
+
# element a primitive, or a nested array
|
308
385
|
else
|
309
|
-
|
310
|
-
|
311
|
-
|
386
|
+
is_nested_array = elem.instance_of?(Array) &&
|
387
|
+
elem.any? { |e| e.instance_of?(Array) }
|
388
|
+
if is_nested_array
|
389
|
+
@left_bracket_offset = \
|
390
|
+
elem.take_while { |e| e.instance_of?(Array) }.size
|
312
391
|
end
|
313
392
|
|
314
|
-
str << (indent * 2)
|
315
|
-
str <<
|
316
|
-
str << ",\n" unless node.index(elem) == (node.length - 1)
|
393
|
+
str << (indent * 2) << node_to_str(elem)
|
394
|
+
str << ",\n" unless node.index(elem) == node.length.pred
|
317
395
|
end
|
318
396
|
end
|
319
397
|
|
@@ -322,50 +400,48 @@ module TidyJson
|
|
322
400
|
elsif node.instance_of?(Hash)
|
323
401
|
str << "{\n"
|
324
402
|
|
403
|
+
# format elements as key-value pairs
|
325
404
|
node.each_with_index do |h, idx|
|
405
|
+
# format values which are hashes themselves
|
326
406
|
if h.last.instance_of?(Hash)
|
327
407
|
key = if h.first.eql? ''
|
328
408
|
"#{indent * 2}\"<##{h.last.class.name.downcase}>\": "
|
329
409
|
else
|
330
410
|
"#{indent * 2}\"#{h.first}\": "
|
331
411
|
end
|
332
|
-
|
333
|
-
str << "{\n"
|
412
|
+
|
413
|
+
str << key << "{\n"
|
334
414
|
|
335
415
|
h.last.each_with_index do |inner_h, inner_h_idx|
|
336
416
|
str << "#{indent * 3}\"#{inner_h.first}\": "
|
337
417
|
str << node_to_str(inner_h.last, 4)
|
338
|
-
str << ",\n" unless inner_h_idx ==
|
418
|
+
str << ",\n" unless inner_h_idx == h.last.to_a.length.pred
|
339
419
|
end
|
340
420
|
|
341
421
|
str << "\n#{indent * 2}}"
|
422
|
+
|
423
|
+
# format plain values
|
342
424
|
else
|
343
|
-
str << "#{indent * 2}\"#{h.first}\": "
|
344
|
-
str << node_to_str(h.last)
|
425
|
+
str << "#{indent * 2}\"#{h.first}\": " << node_to_str(h.last)
|
345
426
|
end
|
346
427
|
|
347
|
-
str << ",\n" unless idx ==
|
428
|
+
str << ",\n" unless idx == node.to_a.length.pred
|
348
429
|
end
|
349
430
|
|
350
431
|
str << "\n#{indent}}"
|
351
|
-
str << ', ' unless
|
352
|
-
((obj.length > 1) && \
|
353
|
-
(obj.instance_of?(Hash) && \
|
354
|
-
(obj.key(obj.values.last) === obj.key(node))) || \
|
355
|
-
(obj.instance_of?(Array) && (obj.last == node)))
|
432
|
+
str << ', ' unless is_last
|
356
433
|
str << "\n"
|
357
434
|
|
435
|
+
# format primitive types
|
358
436
|
else
|
359
437
|
str << node_to_str(node)
|
360
|
-
str << ', ' unless
|
361
|
-
((obj.length > 1) && \
|
362
|
-
(obj.instance_of?(Hash) && \
|
363
|
-
(obj.key(obj.values.last) === obj.key(node))) || \
|
364
|
-
(obj.instance_of?(Array) && (obj.last === node)))
|
438
|
+
str << ', ' unless is_last
|
365
439
|
str << "\n"
|
366
440
|
end
|
367
441
|
|
368
|
-
str.gsub(/(#{indent})+[\n\r]+/, '')
|
442
|
+
str.gsub(/(#{indent})+[\n\r]+/, '')
|
443
|
+
.gsub(/\}\,+/, '},')
|
444
|
+
.gsub(/\]\,+/, '],')
|
369
445
|
end
|
370
446
|
# ~Formatter#format_node
|
371
447
|
|
@@ -389,6 +465,7 @@ module TidyJson
|
|
389
465
|
if node.nil? then graft << 'null'
|
390
466
|
|
391
467
|
elsif node.instance_of?(Hash)
|
468
|
+
|
392
469
|
format_node(node, node).scan(/.*$/) do |n|
|
393
470
|
graft << "\n" << indent << n
|
394
471
|
end
|
@@ -407,7 +484,7 @@ module TidyJson
|
|
407
484
|
|
408
485
|
graft.strip
|
409
486
|
end
|
410
|
-
# ~Formatter
|
487
|
+
# ~Formatter#node_to_str
|
411
488
|
end
|
412
489
|
# ~Formatter
|
413
490
|
|
data/lib/tidy_json/dedication.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module TidyJson # :nodoc:
|
4
|
-
DEDICATION = "\n#{'.' *
|
5
|
-
"#{'.' *
|
6
|
-
"#{'.' *
|
7
|
-
"#{'.' *
|
8
|
-
"#{'.' *
|
9
|
-
"#{'.' *
|
10
|
-
"#{'.' *
|
11
|
-
"#{'.' *
|
4
|
+
DEDICATION = "\n#{'.' * 52}\n" \
|
5
|
+
"#{'.' * 14} This gem is dedicated " \
|
6
|
+
"#{'.' * 15}\n" \
|
7
|
+
"#{'.' * 17} to the memory of #{'.' * 17}\n#{'.' * 52}\n" \
|
8
|
+
"#{'.' * 17} MICHAEL DI PARDO #{'.' * 17}\n#{'.' * 52}\n" \
|
9
|
+
"#{'.' * 12} Please consider supporting #{'.' * 12}\n" \
|
10
|
+
"#{'.' * 13} the MS Society of Canada #{'.' * 13}\n" \
|
11
|
+
"#{'.' * 52}\n" \
|
12
|
+
"#{'.' * 8} https://mssociety.ca/get-involved #{'.' * 9}\n" \
|
13
|
+
"#{'.' * 52}\n\n"
|
12
14
|
end
|
data/lib/tidy_json/version.rb
CHANGED
data/test/test_tidy_json.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test/unit'
|
2
4
|
require 'tidy_json'
|
3
5
|
|
4
6
|
##
|
@@ -13,7 +15,7 @@ class JsonableObject
|
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
|
-
class TidyJsonTest <
|
18
|
+
class TidyJsonTest < Test::Unit::TestCase
|
17
19
|
@@t = JsonableObject.new
|
18
20
|
@@t2 = JsonableObject.new
|
19
21
|
@@t3 = JsonableObject.new
|
@@ -27,8 +29,44 @@ class TidyJsonTest < Minitest::Test
|
|
27
29
|
end
|
28
30
|
|
29
31
|
def test_tidy_static
|
30
|
-
assert_equal(
|
31
|
-
|
32
|
+
assert_equal("{\n \"a\": \"one\", \n \"A\": \"ONE\", \n \"b\": null\n}\n",
|
33
|
+
TidyJson.tidy(a: 'one', A: 'ONE', b: nil))
|
34
|
+
assert_equal(4, TidyJson.tidy({}).length)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_sort_keys_static
|
38
|
+
hash = { c: 3, d: { i: '34', ii: '35', f: 56, a: 9 }, a: 1, b: 2 }
|
39
|
+
hash_array = [{ c: 3, d: { i: '34', ii: '35', f: 56, a: 9 } }, { a: 1 }, { b: 2 }]
|
40
|
+
assert_equal({ a: 1, b: 2, c: 3, d: { a: 9, f: 56, i: '34', ii: '35' } },
|
41
|
+
TidyJson.sort_keys(hash))
|
42
|
+
assert_equal([{ a: 1 }, { b: 2 }, { c: 3, d: { a: 9, f: 56, i: '34', ii: '35' } }],
|
43
|
+
TidyJson.sort_keys(hash_array))
|
44
|
+
assert_equal({ a: 'one', b: 'two', c: 3 },
|
45
|
+
TidyJson.sort_keys('b': 'two', 'c': 3, 'a': 'one'))
|
46
|
+
assert_equal([], TidyJson.sort_keys([]), 'return empty arrays unchanged')
|
47
|
+
assert_equal({}, TidyJson.sort_keys({}), 'return empty hashes unchanged')
|
48
|
+
assert_equal([3, 2, 1], TidyJson.sort_keys([3, 2, 1]),
|
49
|
+
'return arrays of keyless objects unchanged')
|
50
|
+
assert_equal([{ b: 'two' }, 'one'],
|
51
|
+
TidyJson.sort_keys([{ 'b': 'two' }, 'one']),
|
52
|
+
'arrays with any keyless objects should be returned unchanged')
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_sort_keys_instance
|
56
|
+
flat_hash_array = [{ c: 3 }, { a: 1 }, { b: 2 }]
|
57
|
+
nested_hash_array = [{ c: 3, d: { i: '34', ii: '35', f: 56, a: 9 } }, { a: 1 }, { b: 2 }]
|
58
|
+
assert_equal("[\n {\n \"a\": 1\n }, \n {\n \"b\": 2\n }, \n {\n \"c\": 3\n }\n]\n",
|
59
|
+
flat_hash_array.to_tidy_json(sort: true))
|
60
|
+
assert_equal("[\n {\n \"a\": 1\n }, \n {\n \"b\": 2\n }, \n {\n \"c\": 3,\n \"d\": {\n \"a\": 9,\n \"f\": 56,\n \"i\": \"34\",\n \"ii\": \"35\"\n }\n }\n]\n",
|
61
|
+
nested_hash_array.to_tidy_json(indent: 8, sort: true))
|
62
|
+
assert_equal("{\n \"a\": \"one\", \n \"b\": \"two\", \n \"c\": 3\n}\n",
|
63
|
+
{ 'b': 'two', 'c': 3, 'a': 'one' }.to_tidy_json(indent: 6, sort: true))
|
64
|
+
assert_equal("[\n]\n", [].to_tidy_json(sort: true))
|
65
|
+
assert_equal("{\n}\n", {}.to_tidy_json(sort: true))
|
66
|
+
assert_equal("[\n 3, \n 2, \n 1\n]\n",
|
67
|
+
[3, 2, 1].to_tidy_json(indent: 8, sort: true))
|
68
|
+
assert_equal("[\n {\n \"b\": \"two\"\n }, \n \"one\"\n]\n",
|
69
|
+
[{ 'b': 'two' }, 'one'].to_tidy_json(indent: 4, sort: true))
|
32
70
|
end
|
33
71
|
|
34
72
|
def test_tidy_instance
|
@@ -43,26 +81,46 @@ class TidyJsonTest < Minitest::Test
|
|
43
81
|
end
|
44
82
|
|
45
83
|
def test_writers
|
46
|
-
|
84
|
+
json_array = []
|
85
|
+
assert_nothing_thrown '#stringify returns valid JSON' do
|
86
|
+
3.times { |_| json_array << JSON.parse(@@t.stringify) }
|
87
|
+
end
|
88
|
+
|
89
|
+
output = json_array.write_json
|
47
90
|
assert(File.exist?(output))
|
48
|
-
|
91
|
+
assert_nothing_thrown 'Raw JSON should be valid' do
|
92
|
+
File.open(output, 'r') { |f| JSON.parse(f.read) }
|
93
|
+
end
|
94
|
+
|
95
|
+
pretty_output = \
|
96
|
+
json_array.write_json('prettified', tidy: true, sort: true, indent: 8)
|
49
97
|
assert(File.exist?(pretty_output))
|
98
|
+
assert_nothing_thrown 'Formatted JSON should be valid' do
|
99
|
+
File.open(pretty_output, 'r') { |f| JSON.parse(f.read) }
|
100
|
+
end
|
50
101
|
end
|
51
102
|
|
52
103
|
def test_indent_bounds_checking
|
53
|
-
assert_equal(
|
104
|
+
assert_equal("{\n \"a\": \"one\", \n \"b\": \"two\", \n \"c\": 3\n}\n",
|
105
|
+
{ 'b': 'two', 'c': 3, 'a': 'one' }.to_tidy_json(indent: 5, sort: true),
|
106
|
+
'odd values should fall back to default of 2')
|
107
|
+
assert_equal([].to_tidy_json(indent: '16'), "[\n]\n",
|
108
|
+
'values > 12 should fall back to default of 2')
|
54
109
|
assert_equal('Object'.to_tidy_json(indent: []), '')
|
55
110
|
assert_equal(0.to_tidy_json(indent: -89), '')
|
56
111
|
assert_equal(3.1425.to_tidy_json(indent: 3.1425), '')
|
57
112
|
assert_equal(''.to_tidy_json(indent: +0), '')
|
58
113
|
assert_equal([].to_tidy_json(indent: -8.00009), "[\n]\n")
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
114
|
+
assert_nothing_thrown '#stringify should return valid JSON even when ' \
|
115
|
+
'format options are invalid' do
|
116
|
+
assert_equal(JSON.parse(Object.new.stringify).to_tidy_json(indent: nil),
|
117
|
+
"{\n \"class\": \"Object\"\n}\n")
|
118
|
+
assert_equal(JSON.parse(''.stringify).to_tidy_json(indent: -16.009),
|
119
|
+
"{\n \"class\": \"String\"\n}\n")
|
120
|
+
assert_equal(JSON.parse({}.stringify).to_tidy_json(indent: '8'),
|
121
|
+
"{\n \"class\": \"Hash\"\n}\n")
|
122
|
+
assert_equal(JSON.parse(%w[k l m].stringify).to_tidy_json(indent: '<<'),
|
123
|
+
"{\n \"class\": \"Array\"\n}\n")
|
124
|
+
end
|
67
125
|
end
|
68
126
|
end
|
data/tidy_json.gemspec
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'lib/tidy_json/version'
|
2
4
|
require_relative 'lib/tidy_json/dedication'
|
3
5
|
|
@@ -8,9 +10,9 @@ Gem::Specification.new do |spec|
|
|
8
10
|
spec.summary = 'Serialize any Ruby object as readable JSON'
|
9
11
|
spec.description = 'A mixin providing (recursive) JSON serialization and pretty printing.'
|
10
12
|
spec.authors = ['Robert Di Pardo']
|
11
|
-
spec.email = '
|
13
|
+
spec.email = 'dipardo.r@gmail.com'
|
12
14
|
spec.homepage = 'https://github.com/rdipardo/tidy_json'
|
13
|
-
spec.metadata = { 'documentation_uri' => 'https://rubydoc.org/github/rdipardo/tidy_json' }
|
15
|
+
spec.metadata = { 'documentation_uri' => 'https://rubydoc.org/github/rdipardo/tidy_json/master' }
|
14
16
|
spec.license = 'MIT'
|
15
17
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
16
18
|
['.yardopts'].concat(`git ls-files -z`.split("\x0").reject { |f| f.match(/^(\.[\w+\.]+|test|spec|features)/) })
|
@@ -19,7 +21,7 @@ Gem::Specification.new do |spec|
|
|
19
21
|
spec.require_paths = ['lib']
|
20
22
|
spec.required_ruby_version = Gem::Requirement.new('>= 2.3')
|
21
23
|
spec.add_runtime_dependency 'json', '~> 2.2'
|
22
|
-
spec.add_development_dependency '
|
24
|
+
spec.add_development_dependency 'test-unit', '~> 3.3'
|
23
25
|
spec.add_development_dependency 'yard', '~> 0.9'
|
24
26
|
spec.rdoc_options = ['-x test/*']
|
25
27
|
spec.post_install_message = TidyJson::DEDICATION
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tidy_json
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Di Pardo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-06-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -25,19 +25,19 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '2.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: test-unit
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '3.3'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '3.3'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: yard
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0.9'
|
55
55
|
description: A mixin providing (recursive) JSON serialization and pretty printing.
|
56
|
-
email:
|
56
|
+
email: dipardo.r@gmail.com
|
57
57
|
executables: []
|
58
58
|
extensions: []
|
59
59
|
extra_rdoc_files: []
|
@@ -72,18 +72,20 @@ homepage: https://github.com/rdipardo/tidy_json
|
|
72
72
|
licenses:
|
73
73
|
- MIT
|
74
74
|
metadata:
|
75
|
-
documentation_uri: https://rubydoc.org/github/rdipardo/tidy_json
|
75
|
+
documentation_uri: https://rubydoc.org/github/rdipardo/tidy_json/master
|
76
76
|
post_install_message: |2+
|
77
77
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
78
|
+
....................................................
|
79
|
+
.............. This gem is dedicated ...............
|
80
|
+
................. to the memory of .................
|
81
|
+
....................................................
|
82
|
+
................. MICHAEL DI PARDO .................
|
83
|
+
....................................................
|
84
|
+
............ Please consider supporting ............
|
85
|
+
............. the MS Society of Canada .............
|
86
|
+
....................................................
|
87
|
+
........ https://mssociety.ca/get-involved .........
|
88
|
+
....................................................
|
87
89
|
|
88
90
|
rdoc_options:
|
89
91
|
- "-x test/*"
|
@@ -100,7 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
102
|
- !ruby/object:Gem::Version
|
101
103
|
version: '0'
|
102
104
|
requirements: []
|
103
|
-
rubygems_version: 3.0.
|
105
|
+
rubygems_version: 3.0.8
|
104
106
|
signing_key:
|
105
107
|
specification_version: 4
|
106
108
|
summary: Serialize any Ruby object as readable JSON
|