tidy_json 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +3 -1
- data/LICENSE +1 -1
- data/README.md +47 -44
- data/lib/tidy_json.rb +24 -374
- data/lib/tidy_json/dedication.rb +8 -2
- data/lib/tidy_json/formatter.rb +237 -0
- data/lib/tidy_json/serializer.rb +118 -0
- data/lib/tidy_json/version.rb +1 -1
- data/test/JsonableObject.json +1 -1
- data/test/codecov_runner.rb +1 -1
- data/test/test_tidy_json.rb +48 -34
- data/tidy_json.gemspec +3 -3
- metadata +18 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 63e286aa3b914d96b71dc8e32904e0ca0514deb1159bf9a8ef40978f7b2a2ea0
|
4
|
+
data.tar.gz: f7f30ca7fab2e95b93b4aa385d5e2a4752899b273a160e8a64151c68248fb0d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a55fad57a8ffd7d303bb19cbd2f89665faad9089650d7324809fb7c4f1553c27b7783d6e608d0c284b3e91617801ae100a391bb11d142d023d3f037914ed8a0
|
7
|
+
data.tar.gz: 612e14c4480664a0ddb64cc8caac22915a60ca3709e3b1f5cae974eae050333104c5066ba513f70c1c5686dbc096f8c2663853126d4df2bd74d62860d4375ee3
|
data/.yardopts
CHANGED
data/LICENSE
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c) 2019-
|
3
|
+
Copyright (c) 2019-2021 Robert Di Pardo
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# tidy_json
|
2
2
|
|
3
|
-
[![Travis CI][travis_build_status_badge]][travis_build_status] [![Circle CI][cci_build_status_badge]][cci_build_status] [![codecov][codecov_badge]][codecov_status]
|
3
|
+
![Gem Version][gem_version_badge] ![Downloads][gem_downloads] [![Travis CI][travis_build_status_badge]][travis_build_status] [![Circle CI][cci_build_status_badge]][cci_build_status] [![codecov][codecov_badge]][codecov_status]
|
4
4
|
|
5
5
|
A mixin providing (recursive) JSON serialization and pretty printing.
|
6
6
|
|
@@ -19,6 +19,13 @@ gem 'tidy_json'
|
|
19
19
|
# ...
|
20
20
|
```
|
21
21
|
|
22
|
+
### Formatting Options
|
23
|
+
|
24
|
+
As of version [0.3.0][], most of the same options accepted by [`JSON.generate`][]
|
25
|
+
can be passed to `#write_json`, `#to_tidy_json`, or `TidyJson.tidy`.
|
26
|
+
|
27
|
+
See [the docs][] for a current list of options and their default values.
|
28
|
+
|
22
29
|
### Example
|
23
30
|
|
24
31
|
```ruby
|
@@ -28,26 +35,26 @@ class Jsonable
|
|
28
35
|
attr_reader :a, :b
|
29
36
|
def initialize
|
30
37
|
@a = { a: 'uno', f: ['I', 'II', 'III', ['i.', 'ii.', 'iii.', { 'ichi': "\u{4e00}", 'ni': "\u{4e8c}", 'san': "\u{4e09}", 'yon': "\u{56db}" }]], c: {}, b: 'dos', e: [[]] }
|
31
|
-
@b = { z: { iv: 4, ii: '
|
38
|
+
@b = { z: { iv: 4, ii: 'dos', iii: 3, i: 'uno' }, b: ['deux', 3, '<abbr title="four">IV</abbr>'], a: 1, g: [{ none: [] }], f: %w[x y z] }
|
32
39
|
end
|
33
40
|
end
|
34
41
|
|
35
42
|
my_jsonable = Jsonable.new
|
36
|
-
# => #<Jsonable:
|
43
|
+
# => #<Jsonable:0x000055790c93e768 @a={:a=>"uno", :f=>["I", "II", "III", ["i.", "ii.", "iii.", {:ichi=>"一", :ni=>"二", :san=>"三", :yon=>"四"}]], :c=>{}, :b=>"dos", :e=>[[]]}, @b={:z=>{:iv=>4, :ii=>"dos", :iii=>3, :i=>"uno"}, :b=>["deux", 3, "<abbr title=\"four\">IV</abbr>"], :a=>1, :g=>[{:none=>[]}], :f=>["x", "y", "z"]}>
|
37
44
|
|
38
45
|
JSON.parse my_jsonable.stringify
|
39
|
-
# => {"class"
|
46
|
+
# => "{\"class\":\"Jsonable\",\"a\":{\"a\":\"uno\",\"f\":[\"I\",\"II\",\"III\",[\"i.\",\"ii.\",\"iii.\",{\"ichi\":\"一\",\"ni\":\"二\",\"san\":\"三\",\"yon\":\"四\"}]],\"c\":{},\"b\":\"dos\",\"e\":[[]]},\"b\":{\"z\":{\"iv\":4,\"ii\":\"dos\",\"iii\":3,\"i\":\"uno\"},\"b\":[\"deux\",3,\"<abbr title=\\\"four\\\">IV</abbr>\"],\"a\":1,\"g\":[{\"none\":[]}],\"f\":[\"x\",\"y\",\"z\"]}}"
|
40
47
|
|
41
|
-
puts my_jsonable.to_tidy_json(indent: 4, sort: true)
|
48
|
+
puts my_jsonable.to_tidy_json(indent: 4, sort: true, space_before: 2, ascii_only: true)
|
42
49
|
# {
|
43
|
-
# "a": {
|
44
|
-
# "a": "uno",
|
45
|
-
# "b": "dos",
|
46
|
-
# "c": {},
|
47
|
-
# "e": [
|
50
|
+
# "a" : {
|
51
|
+
# "a" : "uno",
|
52
|
+
# "b" : "dos",
|
53
|
+
# "c" : {},
|
54
|
+
# "e" : [
|
48
55
|
# []
|
49
56
|
# ],
|
50
|
-
# "f": [
|
57
|
+
# "f" : [
|
51
58
|
# "I",
|
52
59
|
# "II",
|
53
60
|
# "III",
|
@@ -56,62 +63,58 @@ puts my_jsonable.to_tidy_json(indent: 4, sort: true)
|
|
56
63
|
# "ii.",
|
57
64
|
# "iii.",
|
58
65
|
# {
|
59
|
-
# "ichi": "
|
60
|
-
# "ni": "
|
61
|
-
# "san": "
|
62
|
-
# "yon": "
|
66
|
+
# "ichi" : "\u4e00",
|
67
|
+
# "ni" : "\u4e8c",
|
68
|
+
# "san" : "\u4e09",
|
69
|
+
# "yon" : "\u56db"
|
63
70
|
# }
|
64
71
|
# ]
|
65
72
|
# ]
|
66
73
|
# },
|
67
|
-
# "b": {
|
68
|
-
# "a": 1,
|
69
|
-
# "b": [
|
70
|
-
# "
|
74
|
+
# "b" : {
|
75
|
+
# "a" : 1,
|
76
|
+
# "b" : [
|
77
|
+
# "deux",
|
71
78
|
# 3,
|
72
79
|
# "<abbr title=\"four\">IV</abbr>"
|
73
80
|
# ],
|
74
|
-
# "f": [
|
81
|
+
# "f" : [
|
75
82
|
# "x",
|
76
83
|
# "y",
|
77
84
|
# "z"
|
78
85
|
# ],
|
79
|
-
# "g": [
|
86
|
+
# "g" : [
|
80
87
|
# {
|
81
|
-
# "none": []
|
88
|
+
# "none" : []
|
82
89
|
# }
|
83
90
|
# ],
|
84
|
-
# "z": {
|
85
|
-
# "i": "
|
86
|
-
# "ii": "
|
87
|
-
# "iii": 3,
|
88
|
-
# "iv": 4
|
91
|
+
# "z" : {
|
92
|
+
# "i" : "uno",
|
93
|
+
# "ii" : "dos",
|
94
|
+
# "iii" : 3,
|
95
|
+
# "iv" : 4
|
89
96
|
# }
|
90
97
|
# },
|
91
|
-
# "class": "Jsonable"
|
98
|
+
# "class" : "Jsonable"
|
92
99
|
# }
|
93
100
|
# => nil
|
94
101
|
```
|
95
102
|
|
96
|
-
### Dependencies
|
97
|
-
|
98
|
-
#### Runtime
|
99
|
-
- [json](https://rubygems.org/gems/json) ~> 2.2
|
100
|
-
|
101
|
-
#### Building
|
102
|
-
- [test-unit](https://rubygems.org/gems/test-unit) ~> 3.3
|
103
|
-
- [yard](https://rubygems.org/gems/yard) ~> 0.9
|
104
|
-
|
105
103
|
### License
|
106
|
-
[MIT]
|
104
|
+
Distributed under the terms of the [MIT License][].
|
107
105
|
|
108
106
|
|
109
107
|
[travis_build_status]: https://travis-ci.com/rdipardo/tidy_json
|
110
|
-
[travis_build_status_badge]: https://travis-ci.com/rdipardo/tidy_json.svg?branch=master
|
111
108
|
[cci_build_status]: https://circleci.com/gh/rdipardo/tidy_json/tree/master
|
112
109
|
[cci_build_status_badge]: https://circleci.com/gh/rdipardo/tidy_json.svg?style=svg
|
113
|
-
[
|
114
|
-
[
|
115
|
-
[
|
116
|
-
[gem_version_badge]: https://
|
117
|
-
|
110
|
+
[travis_build_status_badge]: https://travis-ci.com/rdipardo/tidy_json.svg?branch=master
|
111
|
+
[codecov_status]: https://codecov.io/gh/rdipardo/tidy_json/branch/master
|
112
|
+
[codecov_badge]: https://codecov.io/gh/rdipardo/tidy_json/branch/master/graph/badge.svg
|
113
|
+
[gem_version_badge]: https://img.shields.io/gem/v/tidy_json?color=%234ec820&label=gem%20version&logo=ruby&logoColor=%23e9573f
|
114
|
+
[gem_downloads]: https://img.shields.io/gem/dt/tidy_json?logo=ruby&logoColor=%23e9573f
|
115
|
+
[MIT License]: https://github.com/rdipardo/tidy_json/blob/master/LICENSE
|
116
|
+
|
117
|
+
<!-- API spec -->
|
118
|
+
[`JSON.generate`]: https://github.com/flori/json/blob/d49c5de49e54a5ad3f6fcf587f98d63266ef9439/lib/json/pure/generator.rb#L111
|
119
|
+
[the docs]: https://rubydoc.org/github/rdipardo/tidy_json/TidyJson/Formatter#initialize-instance_method
|
120
|
+
[0.3.0]: https://github.com/rdipardo/tidy_json/releases/tag/v0.3.0
|
data/lib/tidy_json.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: false
|
2
2
|
|
3
3
|
require 'json'
|
4
|
+
require_relative 'tidy_json/serializer'
|
5
|
+
require_relative 'tidy_json/formatter'
|
4
6
|
require_relative 'tidy_json/version'
|
5
7
|
|
6
8
|
##
|
@@ -16,31 +18,32 @@ module TidyJson
|
|
16
18
|
# @return [String] A pretty-printed JSON string.
|
17
19
|
def self.tidy(obj = {}, opts = {})
|
18
20
|
formatter = Formatter.new(opts)
|
19
|
-
|
20
|
-
str = ''
|
21
|
+
json = ''
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
str
|
28
|
-
|
29
|
-
|
30
|
-
str << "}\n"
|
23
|
+
begin
|
24
|
+
if obj.instance_variables.empty?
|
25
|
+
obj = sort_keys(obj) if formatter.format[:sorted]
|
26
|
+
json = JSON.generate(obj, formatter.format)
|
27
|
+
else
|
28
|
+
str = "{\n"
|
29
|
+
obj = JSON.parse(obj.stringify)
|
30
|
+
obj = sort_keys(obj) if formatter.format[:sorted]
|
31
31
|
|
32
|
-
|
33
|
-
|
32
|
+
obj.each do |k, v|
|
33
|
+
str << formatter.format[:indent] << "\"#{k}\": "
|
34
|
+
str << formatter.format_node(v, obj)
|
35
|
+
end
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
str << formatter.format_node(v, obj)
|
37
|
+
str << "}\n"
|
38
|
+
json = JSON.generate(JSON.parse(formatter.trim(str)), formatter.format)
|
38
39
|
end
|
39
40
|
|
40
|
-
|
41
|
+
json.gsub(/[\n\r]{2,}/, "\n")
|
42
|
+
.gsub(/\[\s+\]/, '[]')
|
43
|
+
.gsub(/{\s+}/, '{}') << "\n"
|
44
|
+
rescue JSON::JSONError => e
|
45
|
+
warn "#{__FILE__}.#{__LINE__}: #{e.message}"
|
41
46
|
end
|
42
|
-
|
43
|
-
formatter.trim str
|
44
47
|
end
|
45
48
|
|
46
49
|
##
|
@@ -80,7 +83,7 @@ module TidyJson
|
|
80
83
|
temp[idx] = sorter.call(h, {})
|
81
84
|
end
|
82
85
|
|
83
|
-
temp.
|
86
|
+
temp.each_key { |k| sorted << temp[k] }
|
84
87
|
else
|
85
88
|
sorted = sorter.call(obj, {})
|
86
89
|
end
|
@@ -95,11 +98,7 @@ module TidyJson
|
|
95
98
|
# @option (see Formatter#initialize)
|
96
99
|
# @return [String] A pretty-printed JSON string.
|
97
100
|
def to_tidy_json(opts = {})
|
98
|
-
|
99
|
-
TidyJson.tidy(self, opts)
|
100
|
-
else
|
101
|
-
TidyJson.tidy(JSON.parse(stringify), opts)
|
102
|
-
end
|
101
|
+
TidyJson.tidy(self, opts)
|
103
102
|
end
|
104
103
|
|
105
104
|
##
|
@@ -144,356 +143,7 @@ module TidyJson
|
|
144
143
|
rescue Errno::ENOENT, Errno::EACCES, IOError, RuntimeError, NoMethodError => e
|
145
144
|
warn "#{__FILE__}.#{__LINE__}: #{e.message}"
|
146
145
|
end
|
147
|
-
|
148
|
-
##
|
149
|
-
# A purpose-built JSON generator.
|
150
|
-
#
|
151
|
-
# @api private
|
152
|
-
class Serializer
|
153
|
-
##
|
154
|
-
# Searches +obj+ to a maximum depth of 2 for readable attributes, storing
|
155
|
-
# them as key-value pairs in +json_hash+.
|
156
|
-
#
|
157
|
-
# @param obj [Object] A Ruby object that can be parsed as JSON.
|
158
|
-
# @param json_hash [{String,Symbol => #to_s}] Accumulator.
|
159
|
-
# @return [{String => #to_s}] A hash mapping of +obj+'s visible attributes.
|
160
|
-
def self.serialize(obj, json_hash)
|
161
|
-
obj.instance_variables.each do |m|
|
162
|
-
key = m.to_s[/[^\@]\w*/].to_sym
|
163
|
-
|
164
|
-
next unless key && !key.eql?('')
|
165
|
-
|
166
|
-
begin
|
167
|
-
val = obj.send(key) # assuming readable attributes . . .
|
168
|
-
rescue NoMethodError # . . . which may not be always be the case !
|
169
|
-
json_hash[key] = nil
|
170
|
-
end
|
171
|
-
|
172
|
-
begin
|
173
|
-
# process class members of Hash type
|
174
|
-
if val.instance_of?(Hash)
|
175
|
-
nested_key = ''
|
176
|
-
nested = nil
|
177
|
-
|
178
|
-
val.each.any? do |k, v|
|
179
|
-
unless v.instance_variables.empty?
|
180
|
-
nested_key = k
|
181
|
-
nested = v
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
json_hash[key] = val
|
186
|
-
|
187
|
-
if nested
|
188
|
-
pos = val.keys.select { |k| k === nested_key }.first.to_sym
|
189
|
-
nested.instance_variables.each do
|
190
|
-
json_hash[key][pos] = serialize(nested,
|
191
|
-
class: nested.class.name)
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
# process class members of Array type
|
196
|
-
elsif val.instance_of?(Array)
|
197
|
-
json_hash[key] = []
|
198
|
-
|
199
|
-
val.each do |elem|
|
200
|
-
i = val.index(elem)
|
201
|
-
|
202
|
-
# member is a multi-dimensional collection
|
203
|
-
if elem.respond_to?(:each)
|
204
|
-
nested = []
|
205
|
-
elem.each do |e|
|
206
|
-
j = if elem.respond_to?(:key)
|
207
|
-
elem.key(e)
|
208
|
-
else elem.index(e)
|
209
|
-
end
|
210
|
-
|
211
|
-
# nested element is a class object
|
212
|
-
if !e.instance_variables.empty?
|
213
|
-
json_hash[key][j] = { class: e.class.name }
|
214
|
-
|
215
|
-
# recur over the contained object
|
216
|
-
serialize(e, json_hash[key][j])
|
217
|
-
|
218
|
-
# some kind of collection?
|
219
|
-
elsif e.respond_to?(:each)
|
220
|
-
temp = []
|
221
|
-
e.each do |el|
|
222
|
-
temp << if el.instance_variables.empty? then el
|
223
|
-
else JSON.parse(el.stringify)
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
nested << temp
|
228
|
-
|
229
|
-
# scalar type
|
230
|
-
else nested << e
|
231
|
-
end
|
232
|
-
end
|
233
|
-
# ~iteration of nested array elements
|
234
|
-
|
235
|
-
json_hash[key] << nested
|
236
|
-
|
237
|
-
# member is a flat array
|
238
|
-
elsif !elem.instance_variables.empty? # class object?
|
239
|
-
json_hash[key] << { class: elem.class.name }
|
240
|
-
serialize(elem, json_hash[key][i])
|
241
|
-
|
242
|
-
# scalar type
|
243
|
-
else json_hash[key] << elem
|
244
|
-
end
|
245
|
-
end
|
246
|
-
# ~iteration of top-level array elements
|
247
|
-
|
248
|
-
# process any nested class members, i.e., handle a recursive call
|
249
|
-
# to Serializer.serialize
|
250
|
-
elsif obj.index(val) || json_hash.key?(key)
|
251
|
-
if !val.instance_variables.empty?
|
252
|
-
class_elem = { class: val.class.name }
|
253
|
-
json_hash[key] << class_elem
|
254
|
-
k = json_hash[key].index(class_elem)
|
255
|
-
serialize(val, json_hash[key][k])
|
256
|
-
else
|
257
|
-
json_hash[key] << val
|
258
|
-
end
|
259
|
-
|
260
|
-
# process uncollected class members
|
261
|
-
elsif !val.instance_variables.empty? # member is a class object
|
262
|
-
json_hash[key] = { class: val.class.name }
|
263
|
-
serialize(val, json_hash[key])
|
264
|
-
|
265
|
-
# member belongs to a contained object
|
266
|
-
elsif json_hash.key?(key) &&
|
267
|
-
!json_hash[key].has_val?(val) &&
|
268
|
-
json_hash[key].instance_of?(Hash)
|
269
|
-
|
270
|
-
json_hash[key][key] = val
|
271
|
-
|
272
|
-
# scalar member
|
273
|
-
else json_hash[key] = val
|
274
|
-
end
|
275
|
-
rescue NoMethodError
|
276
|
-
# we expected an array to behave like a hash, or vice-versa
|
277
|
-
json_hash.store(key, val) # a shallow copy is better than nothing
|
278
|
-
end
|
279
|
-
end
|
280
|
-
# ~iteration of instance variables
|
281
|
-
|
282
|
-
json_hash
|
283
|
-
end
|
284
|
-
# ~Serializer.serialize
|
285
|
-
end
|
286
|
-
# ~Serializer
|
287
|
-
|
288
|
-
##
|
289
|
-
# A purpose-built JSON formatter.
|
290
|
-
#
|
291
|
-
# @api private
|
292
|
-
class Formatter
|
293
|
-
attr_reader :indent, :sorted
|
294
|
-
# @!attribute indent
|
295
|
-
# @return [String] the string of white space used by this +Formatter+ to
|
296
|
-
# indent object members.
|
297
|
-
|
298
|
-
# @!attribute sorted
|
299
|
-
# @return [Boolean] whether or not this +Formatter+ will sort object
|
300
|
-
# members by key name.
|
301
|
-
|
302
|
-
##
|
303
|
-
# @param opts [Hash] Formatting options.
|
304
|
-
# @option opts [[2,4,6,8,10,12]] :indent (2) An even number of white spaces
|
305
|
-
# to indent each object member.
|
306
|
-
# @option opts [Boolean] :sort (false) Whether or not object members should
|
307
|
-
# be sorted by key.
|
308
|
-
def initialize(opts = {})
|
309
|
-
# The number of times to reduce the left indent of a nested array's
|
310
|
-
# opening bracket
|
311
|
-
@left_bracket_offset = 0
|
312
|
-
|
313
|
-
# True if printing a nested array
|
314
|
-
@need_offset = false
|
315
|
-
|
316
|
-
# don't test for the more explicit :integer? method because it's defined
|
317
|
-
# for floating point numbers also
|
318
|
-
valid_width = opts[:indent].positive? \
|
319
|
-
if opts[:indent].respond_to?(:times) &&
|
320
|
-
(2..12).step(2).include?(opts[:indent])
|
321
|
-
@indent = "\s" * (valid_width ? opts[:indent] : 2)
|
322
|
-
@sorted = opts[:sort] || false
|
323
|
-
end
|
324
|
-
# ~Formatter#initialize
|
325
|
-
|
326
|
-
##
|
327
|
-
# Returns the given +node+ as pretty-printed JSON.
|
328
|
-
#
|
329
|
-
# @param node [#to_s] A visible attribute of +obj+.
|
330
|
-
# @param obj [{Object => #to_s}, <#to_s>] The enumerable object
|
331
|
-
# containing +node+.
|
332
|
-
# @return [String] A formatted string representation of +node+.
|
333
|
-
def format_node(node, obj)
|
334
|
-
str = ''
|
335
|
-
indent = @indent
|
336
|
-
|
337
|
-
is_last = (obj.length <= 1) ||
|
338
|
-
(obj.length > 1 &&
|
339
|
-
(obj.instance_of?(Array) &&
|
340
|
-
!(node === obj.first) &&
|
341
|
-
(obj.size.pred == obj.rindex(node))))
|
342
|
-
|
343
|
-
if node.instance_of?(Array)
|
344
|
-
str << '['
|
345
|
-
str << "\n" unless node.empty?
|
346
|
-
|
347
|
-
# format array elements
|
348
|
-
node.each do |elem|
|
349
|
-
if elem.instance_of?(Hash)
|
350
|
-
str << "#{indent * 2}{"
|
351
|
-
str << "\n" unless elem.empty?
|
352
|
-
|
353
|
-
elem.each_with_index do |inner_h, h_idx|
|
354
|
-
str << "#{indent * 3}\"#{inner_h.first}\": "
|
355
|
-
str << node_to_str(inner_h.last, 4)
|
356
|
-
str << ', ' unless h_idx == elem.to_a.length.pred
|
357
|
-
str << "\n"
|
358
|
-
end
|
359
|
-
|
360
|
-
str << (indent * 2).to_s unless elem.empty?
|
361
|
-
str << '}'
|
362
|
-
str << ',' unless node.index(elem) == node.length.pred
|
363
|
-
str << "\n" unless node.index(elem) == node.length.pred
|
364
|
-
|
365
|
-
# element a scalar, or a nested array
|
366
|
-
else
|
367
|
-
is_nested_array = elem.instance_of?(Array) &&
|
368
|
-
elem.any? { |e| e.instance_of?(Array) }
|
369
|
-
if is_nested_array
|
370
|
-
@left_bracket_offset = \
|
371
|
-
elem.take_while { |e| e.instance_of?(Array) }.size
|
372
|
-
end
|
373
|
-
|
374
|
-
str << (indent * 2) << node_to_str(elem)
|
375
|
-
str << ",\n" unless node.index(elem) == node.length.pred
|
376
|
-
end
|
377
|
-
end
|
378
|
-
|
379
|
-
str << "\n#{indent}" unless node.empty?
|
380
|
-
str << "]\n"
|
381
|
-
|
382
|
-
elsif node.instance_of?(Hash)
|
383
|
-
str << '{'
|
384
|
-
str << "\n" unless node.empty?
|
385
|
-
|
386
|
-
# format elements as key-value pairs
|
387
|
-
node.each_with_index do |h, idx|
|
388
|
-
# format values which are hashes themselves
|
389
|
-
if h.last.instance_of?(Hash)
|
390
|
-
key = if h.first.eql? ''
|
391
|
-
"#{indent * 2}\"<##{h.last.class.name.downcase}>\": "
|
392
|
-
else
|
393
|
-
"#{indent * 2}\"#{h.first}\": "
|
394
|
-
end
|
395
|
-
|
396
|
-
str << key << '{'
|
397
|
-
str << "\n" unless h.last.empty?
|
398
|
-
|
399
|
-
h.last.each_with_index do |inner_h, inner_h_idx|
|
400
|
-
str << "#{indent * 3}\"#{inner_h.first}\": "
|
401
|
-
str << node_to_str(inner_h.last, 4)
|
402
|
-
str << ",\n" unless inner_h_idx == h.last.to_a.length.pred
|
403
|
-
end
|
404
|
-
|
405
|
-
str << "\n#{indent * 2}" unless h.last.empty?
|
406
|
-
str << '}'
|
407
|
-
|
408
|
-
# format scalar values
|
409
|
-
else
|
410
|
-
str << "#{indent * 2}\"#{h.first}\": " << node_to_str(h.last)
|
411
|
-
end
|
412
|
-
|
413
|
-
str << ",\n" unless idx == node.to_a.length.pred
|
414
|
-
end
|
415
|
-
|
416
|
-
str << "\n#{indent}" unless node.empty?
|
417
|
-
str << '}'
|
418
|
-
str << ', ' unless is_last
|
419
|
-
str << "\n"
|
420
|
-
|
421
|
-
# scalars
|
422
|
-
else
|
423
|
-
str << node_to_str(node)
|
424
|
-
str << ', ' unless is_last
|
425
|
-
str << "\n"
|
426
|
-
end
|
427
|
-
|
428
|
-
trim str.gsub(/(#{indent})+[\n\r]+/, '')
|
429
|
-
.gsub(/\}\,+/, '},')
|
430
|
-
.gsub(/\]\,+/, '],')
|
431
|
-
end
|
432
|
-
# ~Formatter#format_node
|
433
|
-
|
434
|
-
##
|
435
|
-
# Returns a JSON-appropriate string representation of +node+.
|
436
|
-
#
|
437
|
-
# @param node [#to_s] A visible attribute of a Ruby object.
|
438
|
-
# @param tabs [Integer] Tab width at which to start printing this node.
|
439
|
-
# @return [String] A formatted string representation of +node+.
|
440
|
-
def node_to_str(node, tabs = 0)
|
441
|
-
graft = ''
|
442
|
-
tabs += 2 if tabs.zero?
|
443
|
-
|
444
|
-
if @need_offset
|
445
|
-
tabs -= 1
|
446
|
-
@left_bracket_offset -= 1
|
447
|
-
end
|
448
|
-
|
449
|
-
indent = @indent * (tabs / 2)
|
450
|
-
|
451
|
-
if node.nil? then graft << 'null'
|
452
|
-
|
453
|
-
elsif node.instance_of?(Hash)
|
454
|
-
|
455
|
-
format_node(node, node).scan(/.*$/) do |n|
|
456
|
-
graft << "\n" << indent << n
|
457
|
-
end
|
458
|
-
|
459
|
-
elsif node.instance_of?(Array)
|
460
|
-
@need_offset = @left_bracket_offset.positive?
|
461
|
-
|
462
|
-
format_node(node, {}).scan(/.*$/) do |n|
|
463
|
-
graft << "\n" << indent << n
|
464
|
-
end
|
465
|
-
|
466
|
-
elsif !node.instance_of?(String) then graft << node.to_s
|
467
|
-
|
468
|
-
else graft << "\"#{node.gsub(/\"/, '\\"')}\""
|
469
|
-
end
|
470
|
-
|
471
|
-
graft.strip
|
472
|
-
end
|
473
|
-
# ~Formatter#node_to_str
|
474
|
-
|
475
|
-
##
|
476
|
-
# Removes any trailing comma from serialized object members.
|
477
|
-
#
|
478
|
-
# @param node [String] A serialized object member.
|
479
|
-
# @return [String] A copy of +node+ without a trailing comma.
|
480
|
-
def trim(node)
|
481
|
-
if (extra_comma = /(?<trail>,\s*[\]\}])$/.match(node))
|
482
|
-
node.sub(extra_comma[:trail],
|
483
|
-
extra_comma[:trail]
|
484
|
-
.slice(1, node.length.pred)
|
485
|
-
.sub(/^\s/, ''))
|
486
|
-
else node
|
487
|
-
end
|
488
|
-
end
|
489
|
-
# ~Formatter#trim
|
490
|
-
end
|
491
|
-
# ~Formatter
|
492
|
-
|
493
|
-
private_constant :Serializer
|
494
|
-
private_constant :Formatter
|
495
146
|
end
|
496
|
-
# ~TidyJson
|
497
147
|
|
498
148
|
##
|
499
149
|
# Includes +TidyJson+ in every Ruby class.
|