tidy_json 0.1.2 → 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/Gemfile +2 -0
- data/LICENSE +1 -1
- data/README.md +79 -55
- data/Rakefile +2 -0
- data/lib/tidy_json.rb +90 -318
- data/lib/tidy_json/dedication.rb +18 -7
- data/lib/tidy_json/formatter.rb +237 -0
- data/lib/tidy_json/serializer.rb +118 -0
- data/lib/tidy_json/version.rb +3 -1
- data/test/JsonableObject.json +1 -0
- data/test/codecov_runner.rb +16 -0
- data/test/test_tidy_json.rb +124 -17
- data/tidy_json.gemspec +8 -5
- metadata +51 -22
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/Gemfile
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
|
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',
|
31
|
-
@b = {
|
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
|
-
|
36
|
-
|
37
|
-
|
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
|
48
|
+
puts my_jsonable.to_tidy_json(indent: 4, sort: true, space_before: 2, ascii_only: true)
|
40
49
|
# {
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
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
|
-
#
|
71
|
-
#
|
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]
|
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
|
-
[
|
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
data/lib/tidy_json.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
17
|
-
|
32
|
+
obj.each do |k, v|
|
33
|
+
str << formatter.format[:indent] << "\"#{k}\": "
|
34
|
+
str << formatter.format_node(v, obj)
|
35
|
+
end
|
18
36
|
|
19
|
-
|
20
|
-
|
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
|
-
|
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
|
-
|
27
|
-
|
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.
|
30
|
-
|
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
|
-
|
86
|
+
temp.each_key { |k| sorted << temp[k] }
|
87
|
+
else
|
88
|
+
sorted = sorter.call(obj, {})
|
34
89
|
end
|
35
90
|
|
36
|
-
|
91
|
+
sorted
|
37
92
|
end
|
38
93
|
|
39
94
|
##
|
40
|
-
# Like +TidyJson::tidy+, but callable by the sender object
|
95
|
+
# Like +TidyJson::tidy+, but callable by the sender object.
|
41
96
|
#
|
42
|
-
# @param
|
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(
|
45
|
-
|
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
|
121
|
+
# Writes a JSON representation of the sender object to the file specified by
|
122
|
+
# +out+.
|
74
123
|
#
|
75
|
-
# @param
|
76
|
-
# @param
|
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(
|
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 =
|
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
|
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.
|