tidy_json 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|