tidy_json 0.1.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16df8d716d6fcdfeba6ed6afbf5954089e172f39b61a73fa853babb5d3c2cde6
4
- data.tar.gz: bc64fa60a34d16af688444e0fb706c7a087dc38ced05babcc0680bf1874fed8f
3
+ metadata.gz: 63e286aa3b914d96b71dc8e32904e0ca0514deb1159bf9a8ef40978f7b2a2ea0
4
+ data.tar.gz: f7f30ca7fab2e95b93b4aa385d5e2a4752899b273a160e8a64151c68248fb0d5
5
5
  SHA512:
6
- metadata.gz: 54185c4b206f63502a061f2a8397a78697fee2eea068c77b1392f02c24d95136ecac03026c1b08b1782c554c0d3356373c367980ce857197f5c3ff2061393663
7
- data.tar.gz: ba130a35229098c25b0e750dc631f725c53013eac9b69a702c26110cd321930c056f13627c066ac805a72851405cdc5203e90638def06e75396970a2c5b52b7d
6
+ metadata.gz: 7a55fad57a8ffd7d303bb19cbd2f89665faad9089650d7324809fb7c4f1553c27b7783d6e608d0c284b3e91617801ae100a391bb11d142d023d3f037914ed8a0
7
+ data.tar.gz: 612e14c4480664a0ddb64cc8caac22915a60ca3709e3b1f5cae974eae050333104c5066ba513f70c1c5686dbc096f8c2663853126d4df2bd74d62860d4375ee3
data/.yardopts CHANGED
@@ -1 +1,3 @@
1
- --private lib/*.rb lib/tidy_json/version.rb - README.md LICENSE
1
+ --exclude lib/tidy_json/dedication.rb
2
+ --private lib/**/*.rb
3
+ --files README.md,LICENSE
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gemspec
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2019-2020 Robert Di Pardo
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
- # TidyJson
1
+ # tidy_json
2
2
 
3
- [![Travis Build Status][travis_build_status_badge]][travis_build_status] [![Circle CI Build Status][cci_build_status_badge]][cci_build_status] ![Gem Version][gem_version_badge]
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
@@ -27,70 +34,87 @@ require 'tidy_json'
27
34
  class Jsonable
28
35
  attr_reader :a, :b
29
36
  def initialize
30
- @a = { a: 'uno', b: 'dos', c: ['I', 'II', 'III', ['i.', 'ii.', 'iii.', { 'ichi': "\u{4e00}", 'ni': "\u{4e8c}", 'san': "\u{4e09}", 'yon': "\u{56db}" }]] }
31
- @b = { a: 1, b: ['two', 3, '<abbr title="four">IV</abbr>'] }
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: [[]] }
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
- complex_object = Jsonable.new
36
- puts JSON.parse complex_object.stringify
37
- # => {"class"=>"Jsonable", "a"=>{"a"=>"uno", "b"=>"dos", "c"=>["I", "II", "III", ["i.", "ii.", "iii.", {"ichi"=>"一", "ni"=>"二", "san"=>"三", "yon"=>"四"}]]}, "b"=>{"a"=>1, "b"=>["two", 3, "<abbr title=\"four\">IV</abbr>"]}}
42
+ my_jsonable = Jsonable.new
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"]}>
44
+
45
+ JSON.parse my_jsonable.stringify
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\"]}}"
38
47
 
39
- puts complex_object.to_tidy_json
48
+ puts my_jsonable.to_tidy_json(indent: 4, sort: true, space_before: 2, ascii_only: true)
40
49
  # {
41
- # "class": "Jsonable",
42
- # "a":
43
- # {
44
- # "a": "uno",
45
- # "b": "dos",
46
- # "c":
47
- # [
48
- # "I",
49
- # "II",
50
- # "III",
51
- # [
52
- # "i.",
53
- # "ii.",
54
- # "iii.",
55
- # {
56
- # "ichi":"一",
57
- # "ni":"",
58
- # "san":"",
59
- # "yon":""
60
- # }
61
- # ]
62
- # ]
63
- # },
64
- # "b":
65
- # {
66
- # "a": 1,
67
- # "b":
68
- # [
69
- # "two",
70
- # 3,
71
- # "<abbr title=\"four\">IV</abbr>"
72
- # ]
73
- # }
50
+ # "a" : {
51
+ # "a" : "uno",
52
+ # "b" : "dos",
53
+ # "c" : {},
54
+ # "e" : [
55
+ # []
56
+ # ],
57
+ # "f" : [
58
+ # "I",
59
+ # "II",
60
+ # "III",
61
+ # [
62
+ # "i.",
63
+ # "ii.",
64
+ # "iii.",
65
+ # {
66
+ # "ichi" : "\u4e00",
67
+ # "ni" : "\u4e8c",
68
+ # "san" : "\u4e09",
69
+ # "yon" : "\u56db"
70
+ # }
71
+ # ]
72
+ # ]
73
+ # },
74
+ # "b" : {
75
+ # "a" : 1,
76
+ # "b" : [
77
+ # "deux",
78
+ # 3,
79
+ # "<abbr title=\"four\">IV</abbr>"
80
+ # ],
81
+ # "f" : [
82
+ # "x",
83
+ # "y",
84
+ # "z"
85
+ # ],
86
+ # "g" : [
87
+ # {
88
+ # "none" : []
89
+ # }
90
+ # ],
91
+ # "z" : {
92
+ # "i" : "uno",
93
+ # "ii" : "dos",
94
+ # "iii" : 3,
95
+ # "iv" : 4
96
+ # }
97
+ # },
98
+ # "class" : "Jsonable"
74
99
  # }
75
100
  # => nil
76
101
  ```
77
102
 
78
- ### Dependencies
79
-
80
- #### Runtime
81
- - [json](https://rubygems.org/gems/json) ~> 2.2
82
-
83
- #### Building
84
- - [bundler](https://rubygems.org/gems/bundler) ~> 1.17
85
- - [minitest](https://rubygems.org/gems/minitest) ~> 5.0
86
- - [yard](https://rubygems.org/gems/yard) ~> 0.9
87
-
88
103
  ### License
89
- [MIT](https://opensource.org/licenses/MIT)
104
+ Distributed under the terms of the [MIT License][].
90
105
 
91
106
 
92
107
  [travis_build_status]: https://travis-ci.com/rdipardo/tidy_json
93
- [cci_build_status]: https://circleci.com/gh/rdipardo/tidy_json
94
- [travis_build_status_badge]: https://travis-ci.com/rdipardo/tidy_json.svg
108
+ [cci_build_status]: https://circleci.com/gh/rdipardo/tidy_json/tree/master
95
109
  [cci_build_status_badge]: https://circleci.com/gh/rdipardo/tidy_json.svg?style=svg
96
- [gem_version_badge]: https://img.shields.io/gem/v/tidy_json
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/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rake/testtask'
3
5
  require 'yard'
@@ -1,4 +1,8 @@
1
+ # frozen_string_literal: false
2
+
1
3
  require 'json'
4
+ require_relative 'tidy_json/serializer'
5
+ require_relative 'tidy_json/formatter'
2
6
  require_relative 'tidy_json/version'
3
7
 
4
8
  ##
@@ -9,48 +13,92 @@ module TidyJson
9
13
  # Emits a pretty-printed JSON representation of the given +obj+.
10
14
  #
11
15
  # @param obj [Object] A Ruby object that can be parsed as JSON.
16
+ # @param opts [Hash] Output format options.
17
+ # @option (see Formatter#initialize)
12
18
  # @return [String] A pretty-printed JSON string.
13
- def self.tidy(obj = {})
14
- str = ''
19
+ def self.tidy(obj = {}, opts = {})
20
+ formatter = Formatter.new(opts)
21
+ json = ''
22
+
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]
15
31
 
16
- if obj.instance_of?(Hash)
17
- str << "{\n"
32
+ obj.each do |k, v|
33
+ str << formatter.format[:indent] << "\"#{k}\": "
34
+ str << formatter.format_node(v, obj)
35
+ end
18
36
 
19
- obj.each do |k, v|
20
- str << "\"#{k}\": "
21
- str << Serializer.format_node(v, obj)
37
+ str << "}\n"
38
+ json = JSON.generate(JSON.parse(formatter.trim(str)), formatter.format)
22
39
  end
23
40
 
24
- str << "}\n"
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}"
46
+ end
47
+ end
25
48
 
26
- elsif obj.instance_of?(Array)
27
- str << "[\n"
49
+ ##
50
+ # Returns the given +obj+ with keys in ascending order to a maximum depth of
51
+ # 2.
52
+ #
53
+ # @param obj [Hash, Array<Hash>] A dictionary-like object or collection
54
+ # thereof.
55
+ # @return [Hash, Array<Hash>, Object] A copy of the given +obj+ with top- and
56
+ # second-level keys in ascending order, or else an identical copy of +obj+.
57
+ # @note +obj+ is returned unchanged if: 1) it's not iterable; 2) it's an
58
+ # empty collection; 3) any one of its elements is not hashable (and +obj+
59
+ # is an array).
60
+ def self.sort_keys(obj = {})
61
+ return obj if !obj.respond_to?(:each) || obj.empty? ||
62
+ (obj.instance_of?(Array) &&
63
+ !obj.all? { |e| e.respond_to? :keys })
64
+
65
+ sorted = {}
66
+ sorter = lambda { |data, ret_val|
67
+ data.keys.sort.each do |k|
68
+ ret_val[k.to_sym] = if data[k].instance_of? Hash
69
+ sorter.call(data[k], {})
70
+ else
71
+ data[k]
72
+ end
73
+ end
74
+
75
+ return ret_val
76
+ }
77
+
78
+ if obj.instance_of? Array
79
+ temp = {}
80
+ sorted = []
28
81
 
29
- obj.each do |v|
30
- str << Serializer.format_node(v, obj)
82
+ (obj.sort_by { |h| h.keys.first }).each_with_index do |h, idx|
83
+ temp[idx] = sorter.call(h, {})
31
84
  end
32
85
 
33
- str << "]\n"
86
+ temp.each_key { |k| sorted << temp[k] }
87
+ else
88
+ sorted = sorter.call(obj, {})
34
89
  end
35
90
 
36
- str
91
+ sorted
37
92
  end
38
93
 
39
94
  ##
40
- # Like +TidyJson::tidy+, but callable by the sender object with the option *not* to pretty-print.
95
+ # Like +TidyJson::tidy+, but callable by the sender object.
41
96
  #
42
- # @param pretty [Boolean] Whether or not the returned string should be pretty-printed.
97
+ # @param opts [Hash] Output format options.
98
+ # @option (see Formatter#initialize)
43
99
  # @return [String] A pretty-printed JSON string.
44
- def to_tidy_json(pretty = true)
45
- if !instance_variables.empty?
46
- if pretty then TidyJson.tidy(JSON.parse(stringify))
47
- else stringify
48
- end
49
- else
50
- if pretty then TidyJson.tidy(self)
51
- else to_json
52
- end
53
- end
100
+ def to_tidy_json(opts = {})
101
+ TidyJson.tidy(self, opts)
54
102
  end
55
103
 
56
104
  ##
@@ -70,308 +118,32 @@ module TidyJson
70
118
  end
71
119
 
72
120
  ##
73
- # Writes a pretty-printed JSON representation of the sender object to the file specified by +out+.
121
+ # Writes a JSON representation of the sender object to the file specified by
122
+ # +out+.
74
123
  #
75
- # @param pretty [Boolean] Whether or not the output should be pretty-printed.
76
- # @param out [String] The destination filename. Defaults to <tt><obj_class_name>_<unix timestamp></tt>.json
124
+ # @param out [String] The destination filename.
125
+ # @param opts [Hash] Output format options.
126
+ # @option (see Formatter#initialize)
127
+ # @option opts [Boolean] :tidy (false) Whether or not the output should be
128
+ # pretty-printed.
77
129
  # @return [String, nil] The path to the written output file, if successful.
78
- def write_json(pretty = true, out = "#{self.class.name}_#{Time.now.to_i}")
130
+ def write_json(out = "#{self.class.name}_#{Time.now.to_i}",
131
+ opts = { tidy: false })
79
132
  path = nil
80
133
 
81
134
  File.open("#{out}.json", 'w') do |f|
82
- path = f << to_tidy_json(pretty)
135
+ path =
136
+ f << if opts[:tidy] then to_tidy_json(opts)
137
+ elsif instance_variables.empty? then to_json
138
+ else stringify
139
+ end
83
140
  end
84
141
 
85
- path.path
86
- rescue IOError, RuntimeError, NoMethodError => e
142
+ path&.path
143
+ rescue Errno::ENOENT, Errno::EACCES, IOError, RuntimeError, NoMethodError => e
87
144
  warn "#{__FILE__}.#{__LINE__}: #{e.message}"
88
145
  end
89
-
90
- ##
91
- # A purpose-built JSON generator.
92
- #
93
- # @api private
94
- class Serializer
95
- ##
96
- # The number of times to reduce the left margin of a nested array's opening
97
- # bracket
98
- @margins_to_backspace = 0
99
-
100
- ##
101
- # True if printing a nested array
102
- @should_backspace = false
103
-
104
- ##
105
- # Searches +obj+ to a *maximum* depth of 2 for readable attributes,
106
- # storing them as key-value pairs in +json_hash+.
107
- #
108
- # @param obj [Object] A Ruby object that can be parsed as JSON.
109
- # @param json_hash [{String,Symbol => #to_s}] Accumulator.
110
- # @return [{String => #to_s}] A hash mapping of +obj+'s visible attributes.
111
- def self.serialize(obj, json_hash)
112
- obj.instance_variables.each do |m|
113
- key = m.to_s[/[^\@]\w*/].to_sym
114
-
115
- next unless key && !key.eql?('')
116
-
117
- begin
118
- val = obj.send(key) # assuming readable attributes . . .
119
- rescue NoMethodError # . . . which may not be always be the case !
120
- json_hash[key] = nil
121
- end
122
-
123
- begin
124
- # process class members of Hash type
125
- if val.instance_of?(Hash)
126
- nested_key = ''
127
- nested = nil
128
-
129
- val.each.any? do |k, v|
130
- if v.instance_variables.first
131
- nested_key = k
132
- nested = v
133
- end
134
- end
135
-
136
- json_hash[key] = val
137
-
138
- if nested
139
- pos = val.keys.select { |k| k === nested_key }.first.to_sym
140
- nested.instance_variables.each do
141
- json_hash[key][pos] = serialize(nested, class: nested.class.name)
142
- end
143
- end
144
-
145
- # process class members of Array type
146
- elsif val.instance_of?(Array)
147
- json_hash[key] = []
148
-
149
- val.each do |elem|
150
- i = val.index(elem)
151
-
152
- # multi-dimensional array
153
- if elem.instance_of?(Array)
154
- nested = []
155
- elem.each do |e|
156
- j = elem.index(e)
157
-
158
- # nested array element is a class object
159
- if e.instance_variables.first
160
- json_hash[key][j] = { class: e.class.name }
161
-
162
- # recur over the contained object
163
- serialize(e, json_hash[key][j])
164
- else
165
- # some kind of collection?
166
- if e.respond_to? :each
167
- temp = []
168
- e.each { |el| temp << el }
169
- nested << temp
170
- else nested << e
171
- end
172
- end
173
- end
174
- # ~iteration of nested array elements
175
-
176
- json_hash[key] << nested
177
-
178
- else
179
- # 1-D array of class objects
180
- if elem.instance_variables.first
181
- json_hash[key] << { class: elem.class.name }
182
- serialize(elem, json_hash[key][i])
183
- else
184
- # element of primitive type (or Array, or Hash):
185
- # leverage 1:1 mapping of Hash:object
186
- if elem.instance_of?(Hash) then json_hash[key] = val
187
- else
188
- # some kind of collection
189
- if elem.respond_to? :each
190
- temp = []
191
- elem.each { |e| temp << e }
192
- json_hash[key] << temp
193
- else json_hash[key] << elem
194
- end
195
- end
196
- end
197
- end
198
- end
199
- # ~iteration of top-level array elements
200
-
201
- # process any nested class members, i.e., handle a recursive call
202
- # to Serializer.serialize
203
- elsif obj.index(val) || json_hash.key?(key)
204
- if val.instance_variables.first
205
- class_elem = { class: val.class.name }
206
- json_hash[key] << class_elem
207
- k = json_hash[key].index(class_elem)
208
- serialize(val, json_hash[key][k])
209
- else
210
- json_hash[key] << val
211
- end
212
-
213
- # process uncollected data members
214
- else
215
- # member a class object
216
- if val.instance_variables.first
217
- json_hash[key] = { class: val.class.name }
218
- serialize(val, json_hash[key])
219
- else
220
- # member a hash element
221
- if json_hash.key?(key) && \
222
- !json_hash[key].has_val?(val) && \
223
- json_hash[key].instance_of?(Hash)
224
-
225
- json_hash[key][key] = val
226
- else
227
- json_hash[key] = val
228
- end
229
- end
230
- end
231
- rescue NoMethodError
232
- # we expected an array to behave like a hash, or vice-versa
233
- json_hash.store(key, val) # a shallow copy is better than nothing
234
- end
235
- end
236
- # ~iteration of instance variables
237
-
238
- json_hash
239
- end
240
- # ~Serializer.serialize
241
-
242
- ##
243
- # Returns the given +node+ as pretty-printed JSON.
244
- #
245
- # @param node [#to_s] A visible attribute of +obj+.
246
- # @param obj [{Object => Object}, <Object>] The enumerable object containing +node+.
247
- # @return [String] A formatted string representation of +node+.
248
- def self.format_node(node, obj)
249
- str = ''
250
-
251
- if node.instance_of?(Array)
252
- str << "\n\t[\n"
253
-
254
- node.each do |elem|
255
- if elem.instance_of?(Hash)
256
- str << "\t\t{\n"
257
-
258
- elem.each_with_index do |inner_h, h_idx|
259
- str << "\t\t\t\"#{inner_h.first}\":"
260
- str << node_to_str(inner_h.last)
261
- str << ', ' unless h_idx == (elem.to_a.length - 1)
262
- str << "\n"
263
- end
264
-
265
- str << "\t\t}"
266
- str << ',' unless node.index(elem) == (node.length - 1)
267
- str << "\n" unless node.index(elem) == (node.length - 1)
268
-
269
- else
270
-
271
- if elem.instance_of?(Array) && elem.any? { |e| e.instance_of?(Array) }
272
- @margins_to_backspace = elem.take_while { |e| e.instance_of?(Array) }.size
273
- end
274
-
275
- str << "\t\t"
276
- str << node_to_str(elem)
277
- str << ",\n" unless node.index(elem) == (node.length - 1)
278
- end
279
- end
280
-
281
- str << "\n\t]\n"
282
-
283
- elsif node.instance_of?(Hash)
284
- str << "\n\t{\n"
285
-
286
- node.each_with_index do |h, idx|
287
- if h.last.instance_of?(Hash)
288
- key = if h.first.eql? ''
289
- "\t\t\"<##{h.last.class.name.downcase}>\":"
290
- else
291
- "\t\t\"#{h.first}\":"
292
- end
293
- str << key
294
- str << "\n\t\t\t{\n"
295
-
296
- h.last.each_with_index do |inner_h, inner_h_idx|
297
- str << "\t\t\t\t\"#{inner_h.first}\":"
298
- str << node_to_str(inner_h.last, 4)
299
- str << ",\n" unless inner_h_idx == (h.last.to_a.length - 1)
300
- end
301
-
302
- str << "\n\t\t\t}"
303
- else
304
- str << "\t\t\"#{h.first}\": "
305
- str << node_to_str(h.last)
306
- end
307
-
308
- str << ",\n" unless idx == (node.to_a.length - 1)
309
- end
310
-
311
- str << "\n\t}"
312
- str << ', ' unless (obj.length <= 1) || \
313
- ((obj.length > 1) && \
314
- (obj.instance_of?(Hash) && \
315
- (obj.key(obj.values.last) === obj.key(node))) || \
316
- (obj.instance_of?(Array) && (obj.last == node)))
317
- str << "\n"
318
-
319
- else
320
- str << node_to_str(node)
321
- str << ', ' unless (obj.length <= 1) || \
322
- ((obj.length > 1) && \
323
- (obj.instance_of?(Hash) && \
324
- (obj.key(obj.values.last) === obj.key(node))) || \
325
- (obj.instance_of?(Array) && (obj.last === node)))
326
- str << "\n"
327
- end
328
-
329
- str.gsub(/\t+[\n\r]+/, '').gsub(/\}\,+/, '},').gsub(/\]\,+/, '],')
330
- end
331
- # ~Serializer.format_node
332
-
333
- ##
334
- # Returns a JSON-appropriate string representation of +node+.
335
- #
336
- # @param node [#to_s] A visible attribute of a Ruby object.
337
- # @param tabs [Fixnum] Tab width at which to start printing this node.
338
- # @return [String] A formatted string representation of +node+.
339
- def self.node_to_str(node, tabs = 0)
340
- graft = ''
341
-
342
- tabs += 2 if tabs.zero?
343
- if @should_backspace
344
- tabs -= 1
345
- @margins_to_backspace -= 1
346
- end
347
-
348
- if node.nil? then graft << 'null'
349
- elsif node.instance_of?(Hash)
350
-
351
- format_node(node, node).scan(/.*$/) do |n|
352
- graft << "\n" << ("\t" * tabs).to_s << n
353
- end
354
-
355
- elsif node.instance_of?(Array)
356
- @should_backspace = @margins_to_backspace.positive?
357
-
358
- format_node(node, {}).scan(/.*$/) do |n|
359
- graft << "\n" << ("\t" * tabs).to_s << n
360
- end
361
-
362
- elsif !node.instance_of?(String) then graft << node.to_s
363
- else graft << "\"#{node.gsub(/\"/, '\\"')}\""
364
- end
365
-
366
- graft.rstrip
367
- end
368
- # ~Serializer.node_to_str
369
- end
370
- # ~Serializer
371
-
372
- private_constant :Serializer
373
146
  end
374
- # ~TidyJson
375
147
 
376
148
  ##
377
149
  # Includes +TidyJson+ in every Ruby class.