tidy_json 0.3.0 → 0.5.1
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 +0 -1
- data/Gemfile +7 -0
- data/LICENSE +1 -1
- data/README.md +38 -8
- data/bin/jtidy +143 -0
- data/bin/jtidy_info.rb +31 -0
- data/lib/tidy_json/formatter.rb +26 -32
- data/lib/tidy_json/serializer.rb +88 -19
- data/lib/tidy_json/version.rb +1 -1
- data/test/codecov_runner.rb +2 -2
- data/test/test_tidy_json.rb +19 -15
- data/tidy_json.gemspec +7 -8
- metadata +11 -56
- data/lib/tidy_json/dedication.rb +0 -20
- data/test/JsonableObject.json +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3074ee12d1a54599a8c9160fc353c5b7969655296769ddf8c43a6ed0a731af77
|
4
|
+
data.tar.gz: 8c5b373ec10867f9b2b3c66c8ee1e14e7973cdd7e21fc0c1de6e832affebf600
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe1e81b90fb55da164698cb56b45232b64d1fcea274beb64f0ea1762e5eea0728904a3c487172d6bf266755b9f962126277d54b185ea6baf81b7029769bcc894
|
7
|
+
data.tar.gz: 0a84a04ba5440f12df61b45cbdc86cef16ce5c3dd2b866eacf5249999873e0c204344c20488c3857fd0aa288fedb388e5c195d2aed8b4c1aa53e52f105df362a
|
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-2022 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
|
-
![Gem Version][gem_version_badge] ![Downloads][gem_downloads] [![Travis CI][travis_build_status_badge]][travis_build_status] [![
|
3
|
+
![Gem Version][gem_version_badge] ![Downloads][gem_downloads] [![Travis CI][travis_build_status_badge]][travis_build_status] [![codecov][codecov_badge]][codecov_status]
|
4
4
|
|
5
5
|
A mixin providing (recursive) JSON serialization and pretty printing.
|
6
6
|
|
@@ -100,21 +100,51 @@ puts my_jsonable.to_tidy_json(indent: 4, sort: true, space_before: 2, ascii_only
|
|
100
100
|
# => nil
|
101
101
|
```
|
102
102
|
|
103
|
+
### Command Line Usage
|
104
|
+
|
105
|
+
After [installing the gem][], pass the name of a file containing JSON to `jtidy`
|
106
|
+
(with or without a file extension). Run `jtidy -h` for a complete list of
|
107
|
+
formatting options:
|
108
|
+
|
109
|
+
```
|
110
|
+
jtidy FILE[.json] [-d out[.json]] [-i [2,4,6,8,10,12]] [-p [1..8]] [-v [1..8]] [-o D] [-a D] [-m N] [-e] [-A] [-N] [-s] [-f] [-P]
|
111
|
+
-d, --dest out[.json] Name of output file
|
112
|
+
-i, --indent [2,4,6,8,10,12] The number of spaces to indent each object member [2]
|
113
|
+
-p, --prop-name-space [1..8] The number of spaces to put after property names [0]
|
114
|
+
-v, --value-space [1..8] The number of spaces to put before property values [1]
|
115
|
+
-o, --object-delim D A string of whitespace to delimit object members [\n]
|
116
|
+
-a, --array-delim D A string of whitespace to delimit array elements [\n]
|
117
|
+
-m, --max-nesting N The maximum level of data structure nesting in the generated JSON; 0 == "no depth checking" [100]
|
118
|
+
-e, --escape Escape /'s [false]
|
119
|
+
-A, --ascii Generate ASCII characters only [false]
|
120
|
+
-N, --nan Allow NaN, Infinity and -Infinity [false]
|
121
|
+
-s, --sort Sort property names [false]
|
122
|
+
-f, --force Overwrite source file [false]
|
123
|
+
-P, --preview Show preview of output [false]
|
124
|
+
-V, --version Show version
|
125
|
+
-h, --help Show this help message
|
126
|
+
```
|
127
|
+
|
128
|
+
### Notice
|
129
|
+
The `jtidy` executable bundled with this gem is in no way affiliated with, nor based on,
|
130
|
+
the HTML parser and pretty printer [of the same name](https://github.com/jtidy/jtidy).
|
131
|
+
|
132
|
+
The JTidy source code and binaries are licensed under the terms of the Zlib-Libpng License.
|
133
|
+
More information is available [here](https://raw.githubusercontent.com/jtidy/jtidy/master/LICENSE.txt).
|
134
|
+
|
103
135
|
### License
|
104
136
|
Distributed under the terms of the [MIT License][].
|
105
137
|
|
106
138
|
|
107
|
-
[travis_build_status]: https://travis-ci.com/rdipardo/tidy_json
|
108
|
-
[
|
109
|
-
[cci_build_status_badge]: https://circleci.com/gh/rdipardo/tidy_json.svg?style=svg
|
110
|
-
[travis_build_status_badge]: https://travis-ci.com/rdipardo/tidy_json.svg?branch=master
|
139
|
+
[travis_build_status]: https://app.travis-ci.com/github/rdipardo/tidy_json
|
140
|
+
[travis_build_status_badge]: https://app.travis-ci.com/rdipardo/tidy_json.svg?branch=master
|
111
141
|
[codecov_status]: https://codecov.io/gh/rdipardo/tidy_json/branch/master
|
112
142
|
[codecov_badge]: https://codecov.io/gh/rdipardo/tidy_json/branch/master/graph/badge.svg
|
113
143
|
[gem_version_badge]: https://img.shields.io/gem/v/tidy_json?color=%234ec820&label=gem%20version&logo=ruby&logoColor=%23e9573f
|
114
144
|
[gem_downloads]: https://img.shields.io/gem/dt/tidy_json?logo=ruby&logoColor=%23e9573f
|
115
|
-
[MIT License]: https://
|
116
|
-
|
145
|
+
[MIT License]: https://raw.githubusercontent.com/rdipardo/tidy_json/master/LICENSE
|
146
|
+
[installing the gem]: https://github.com/rdipardo/tidy_json#installation
|
117
147
|
<!-- API spec -->
|
118
148
|
[`JSON.generate`]: https://github.com/flori/json/blob/d49c5de49e54a5ad3f6fcf587f98d63266ef9439/lib/json/pure/generator.rb#L111
|
119
149
|
[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
|
150
|
+
[0.3.0]: https://github.com/rdipardo/tidy_json/releases/tag/v0.3.0
|
data/bin/jtidy
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require 'tidy_json'
|
6
|
+
require_relative 'jtidy_info'
|
7
|
+
|
8
|
+
class Jtidy # :nodoc:
|
9
|
+
OPTIONS = {
|
10
|
+
'-d out[.json]': [:dest, String, '--dest', 'Name of output file'],
|
11
|
+
'-i [2,4,6,8,10,12]': [:indent, Integer, '--indent [2,4,6,8,10,12]',
|
12
|
+
'The number of spaces to indent each object member [2]'],
|
13
|
+
'-p [1..8]': [:space_before, Integer, '--prop-name-space [1..8]',
|
14
|
+
'The number of spaces to put after property names [0]'],
|
15
|
+
'-v [1..8]': [:space, Integer, '--value-space [1..8]',
|
16
|
+
'The number of spaces to put before property values [1]'],
|
17
|
+
'-o D': [:object_nl, String, '--object-delim D',
|
18
|
+
'A string of whitespace to delimit object members [\n]'],
|
19
|
+
'-a D': [:array_nl, String, '--array-delim D',
|
20
|
+
'A string of whitespace to delimit array elements [\n]'],
|
21
|
+
'-m N': [:max_nesting, Integer, '--max-nesting N',
|
22
|
+
'The maximum level of data structure nesting in the generated ' \
|
23
|
+
'JSON; 0 == "no depth checking" [100]'],
|
24
|
+
'-e': [:escape_slash, nil, '--escape', 'Escape /\'s [false]'],
|
25
|
+
'-A': [:ascii_only, nil, '--ascii', 'Generate ASCII characters only [false]'],
|
26
|
+
'-N': [:allow_nan, nil, '--nan', 'Allow NaN, Infinity and -Infinity [false]'],
|
27
|
+
'-s': [:sort, nil, '--sort', 'Sort property names [false]'],
|
28
|
+
'-f': [:force, nil, '--force', 'Overwrite source file [false]'],
|
29
|
+
# script-only options
|
30
|
+
'-P': [:preview, nil, '--preview', 'Show preview of output [false]']
|
31
|
+
}.freeze
|
32
|
+
|
33
|
+
def self.unescape(str)
|
34
|
+
str.gsub(/\\b|\\h|\\n|\\r|\\s|\\t|\\v/,
|
35
|
+
{
|
36
|
+
'\\b': "\b",
|
37
|
+
'\\h': "\h",
|
38
|
+
'\\n': "\n",
|
39
|
+
'\\r': "\r",
|
40
|
+
'\\s': "\s",
|
41
|
+
'\\t': "\t",
|
42
|
+
'\\v': "\v"
|
43
|
+
})
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.show_unused(opts)
|
47
|
+
return if opts.empty?
|
48
|
+
|
49
|
+
ignored = opts.keys.map do |key|
|
50
|
+
(OPTIONS.keys.select do |k|
|
51
|
+
OPTIONS[k][0].eql? key
|
52
|
+
end.first || '')[0..1]
|
53
|
+
end
|
54
|
+
warn "Ignoring options: #{ignored.join ', '}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.parse(options)
|
58
|
+
format_options = {}
|
59
|
+
OptionParser.new do |opts|
|
60
|
+
opts.banner = \
|
61
|
+
"#{File.basename __FILE__} FILE[.json] " \
|
62
|
+
"#{(OPTIONS.keys.map { |k| "[#{k}]" }).join ' '}"
|
63
|
+
OPTIONS.each_key do |k|
|
64
|
+
opt, type, long_name, desc = OPTIONS[k]
|
65
|
+
opts.on(k, long_name, type, desc) do |v|
|
66
|
+
format_options[opt] = (type == String ? unescape(v) : v)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
opts.on_tail('-V', '--version', 'Show version') do
|
71
|
+
show_unused format_options
|
72
|
+
puts ::JtidyInfo.new.to_s
|
73
|
+
exit 0
|
74
|
+
end
|
75
|
+
|
76
|
+
opts.on_tail('-h', '--help', 'Show this help message') do
|
77
|
+
show_unused format_options
|
78
|
+
puts opts
|
79
|
+
exit 0
|
80
|
+
end
|
81
|
+
end.parse! options
|
82
|
+
|
83
|
+
format_options
|
84
|
+
end
|
85
|
+
|
86
|
+
private_class_method :unescape, :show_unused
|
87
|
+
end
|
88
|
+
|
89
|
+
begin
|
90
|
+
begin
|
91
|
+
OPTS = Jtidy.parse(ARGV).freeze
|
92
|
+
INPUT_FILE = ARGV[0].freeze
|
93
|
+
rescue OptionParser::InvalidOption => e
|
94
|
+
warn e.message.capitalize
|
95
|
+
raise OptionParser::InvalidArgument
|
96
|
+
end
|
97
|
+
|
98
|
+
if INPUT_FILE.nil? || INPUT_FILE.strip.empty?
|
99
|
+
Jtidy.parse %w[--help]
|
100
|
+
|
101
|
+
else
|
102
|
+
tidy = ''
|
103
|
+
fname = INPUT_FILE.strip.gsub('\\', '/')
|
104
|
+
ext = File.extname(fname)
|
105
|
+
input = File.join(
|
106
|
+
File.expand_path(File.dirname(fname)), File.basename(fname, ext)
|
107
|
+
).to_s
|
108
|
+
outfile = unless OPTS[:dest].nil? || OPTS[:dest].strip.empty?
|
109
|
+
fname = OPTS[:dest].strip.gsub('\\', '/')
|
110
|
+
ext = File.extname(fname)
|
111
|
+
File.join(
|
112
|
+
File.expand_path(File.dirname(fname)), File.basename(fname, ext)
|
113
|
+
).to_s
|
114
|
+
end
|
115
|
+
|
116
|
+
begin
|
117
|
+
File.open("#{input}.json", 'r') do |json|
|
118
|
+
begin
|
119
|
+
tidy = TidyJson.tidy(JSON.parse(json.read.strip), OPTS)
|
120
|
+
rescue JSON::JSONError => e
|
121
|
+
warn "#{__FILE__}.#{__LINE__}: #{e.message}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
if tidy.length.positive?
|
126
|
+
output = (if OPTS[:force]
|
127
|
+
outfile.nil? ? input : outfile
|
128
|
+
elsif Regexp.new("(#{outfile})", Regexp::IGNORECASE) =~ input
|
129
|
+
warn "Can't overwrite #{input}.json without '--force' option"
|
130
|
+
"#{input}-tidy"
|
131
|
+
else outfile
|
132
|
+
end) + '.json'
|
133
|
+
File.write(output, tidy)
|
134
|
+
puts "\nWrote: #{output}"
|
135
|
+
puts "#{tidy[0..1024]}\n . . ." if OPTS[:preview]
|
136
|
+
end
|
137
|
+
rescue Errno::ENOENT, Errno::EACCES, IOError => e
|
138
|
+
warn "#{__FILE__}.#{__LINE__}: #{e.message}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
rescue OptionParser::InvalidArgument, OptionParser::MissingArgument
|
142
|
+
Jtidy.parse %w[--help]
|
143
|
+
end
|
data/bin/jtidy_info.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TidyJson
|
4
|
+
class JtidyInfo # :nodoc:
|
5
|
+
NOTICE = [
|
6
|
+
'#',
|
7
|
+
'# jtidy is in no way affiliated with, nor based on, ',
|
8
|
+
'# the HTML parser and pretty printer of the same name.',
|
9
|
+
'#',
|
10
|
+
'# More information is available here:',
|
11
|
+
'# https://github.com/rdipardo/tidy_json#command-line-usage',
|
12
|
+
'#'
|
13
|
+
].join("\n").freeze
|
14
|
+
|
15
|
+
attr_reader :meta
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
gem = Gem::Specification.find_by_name('tidy_json')
|
19
|
+
@meta = {
|
20
|
+
name: "# jtidy #{gem.version}",
|
21
|
+
license: "# License: #{gem.license}",
|
22
|
+
bugs: "# Bugs: #{gem.metadata['bug_tracker_uri']}",
|
23
|
+
notice: NOTICE
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
(@meta.values.join "\n").freeze
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/tidy_json/formatter.rb
CHANGED
@@ -14,16 +14,16 @@ module TidyJson
|
|
14
14
|
# Returns a new instance of +Formatter+.
|
15
15
|
#
|
16
16
|
# @param opts [Hash] Formatting options.
|
17
|
-
# @option opts [[2,4,6,8,10,12]] :indent (2)
|
18
|
-
# indent each object member.
|
19
|
-
# @option opts [[2..8]] :space_before (0) The number of spaces to put
|
20
|
-
# before each +:+ delimiter.
|
21
|
-
# @option opts [[2..8]] :space (1) The number of spaces to put after
|
22
|
-
# each +:+ delimiter.
|
23
|
-
# @option opts [String] :object_nl ("\n") A string to put at the end of
|
17
|
+
# @option opts [[2,4,6,8,10,12]] :indent (2) The number of spaces to indent
|
24
18
|
# each object member.
|
25
|
-
# @option opts [
|
26
|
-
#
|
19
|
+
# @option opts [[1..8]] :space_before (0) The number of spaces to put after
|
20
|
+
# property names.
|
21
|
+
# @option opts [[1..8]] :space (1) The number of spaces to put before
|
22
|
+
# property values.
|
23
|
+
# @option opts [String] :object_nl ("\n") A string of whitespace to delimit
|
24
|
+
# object members.
|
25
|
+
# @option opts [String] :array_nl ("\n") A string of whitespace to delimit
|
26
|
+
# array elements.
|
27
27
|
# @option opts [Numeric] :max_nesting (100) The maximum level of data
|
28
28
|
# structure nesting in the generated JSON. Disable depth checking by
|
29
29
|
# passing +max_nesting: 0+.
|
@@ -31,12 +31,12 @@ module TidyJson
|
|
31
31
|
# slash (/) should be escaped.
|
32
32
|
# @option opts [Boolean] :ascii_only (false) Whether or not only ASCII
|
33
33
|
# characters should be generated.
|
34
|
-
# @option opts [Boolean] :allow_nan (false) Whether or not +NaN+,
|
35
|
-
# +Infinity+ and +-Infinity
|
36
|
-
#
|
34
|
+
# @option opts [Boolean] :allow_nan (false) Whether or not to allow +NaN+,
|
35
|
+
# +Infinity+ and +-Infinity+. If +false+, an exception is thrown if one
|
36
|
+
# of these values is encountered.
|
37
37
|
# @option opts [Boolean] :sort (false) Whether or not object members should
|
38
|
-
# be sorted by
|
39
|
-
# @see https://github.com/flori/json/blob/
|
38
|
+
# be sorted by property name.
|
39
|
+
# @see https://github.com/flori/json/blob/b8c1c640cd375f2e2ccca1b18bf943f80ad04816/lib/json/pure/generator.rb#L111 JSON::Pure::Generator
|
40
40
|
def initialize(opts = {})
|
41
41
|
# The number of times to reduce the left indent of a nested array's
|
42
42
|
# opening bracket
|
@@ -46,18 +46,19 @@ module TidyJson
|
|
46
46
|
@need_offset = false
|
47
47
|
|
48
48
|
valid_indent = (2..12).step(2).include?(opts[:indent])
|
49
|
-
valid_space_before = (
|
50
|
-
valid_space_after = (
|
49
|
+
valid_space_before = (1..8).include?(opts[:space_before])
|
50
|
+
valid_space_after = (1..8).include?(opts[:space])
|
51
51
|
# don't test for the more explicit :integer? method because it's defined
|
52
52
|
# for floating point numbers also
|
53
53
|
valid_depth = opts[:max_nesting] >= 0 \
|
54
54
|
if opts[:max_nesting].respond_to?(:times)
|
55
|
+
valid_newline = ->(str) { str.respond_to?(:strip) && str.strip.empty? }
|
55
56
|
@format = {
|
56
57
|
indent: "\s" * (valid_indent ? opts[:indent] : 2),
|
57
58
|
space_before: "\s" * (valid_space_before ? opts[:space_before] : 0),
|
58
59
|
space: "\s" * (valid_space_after ? opts[:space] : 1),
|
59
|
-
object_nl: opts[:object_nl]
|
60
|
-
array_nl: opts[:array_nl]
|
60
|
+
object_nl: (valid_newline.call(opts[:object_nl]) ? opts[:object_nl] : "\n"),
|
61
|
+
array_nl: (valid_newline.call(opts[:array_nl]) ? opts[:array_nl] : "\n"),
|
61
62
|
max_nesting: valid_depth ? opts[:max_nesting] : 100,
|
62
63
|
escape_slash: opts[:escape_slash] || false,
|
63
64
|
ascii_only: opts[:ascii_only] || false,
|
@@ -79,10 +80,8 @@ module TidyJson
|
|
79
80
|
indent = @format[:indent]
|
80
81
|
|
81
82
|
is_last = (obj.length <= 1) ||
|
82
|
-
(obj.
|
83
|
-
(obj.
|
84
|
-
!(node === obj.first) &&
|
85
|
-
(obj.size.pred == obj.rindex(node))))
|
83
|
+
(obj.instance_of?(Array) &&
|
84
|
+
(obj.size.pred == obj.rindex(node)))
|
86
85
|
|
87
86
|
if node.instance_of?(Array)
|
88
87
|
str << '['
|
@@ -131,12 +130,7 @@ module TidyJson
|
|
131
130
|
node.each_with_index do |h, idx|
|
132
131
|
# format values which are hashes themselves
|
133
132
|
if h.last.instance_of?(Hash)
|
134
|
-
key =
|
135
|
-
"#{indent * 2}\"<##{h.last.class.name.downcase}>\": "
|
136
|
-
else
|
137
|
-
"#{indent * 2}\"#{h.first}\": "
|
138
|
-
end
|
139
|
-
|
133
|
+
key = "#{indent * 2}\"#{h.first || "<##{h.last.class.name.downcase}>"}\": "
|
140
134
|
str << key << '{'
|
141
135
|
str << "\n" unless h.last.empty?
|
142
136
|
|
@@ -170,8 +164,8 @@ module TidyJson
|
|
170
164
|
end
|
171
165
|
|
172
166
|
trim str.gsub(/(#{indent})+[\n\r]+/, '')
|
173
|
-
.gsub(/\}
|
174
|
-
.gsub(/\]
|
167
|
+
.gsub(/\},+/, '},')
|
168
|
+
.gsub(/\],+/, '],')
|
175
169
|
end
|
176
170
|
# ~Formatter#format_node
|
177
171
|
|
@@ -209,7 +203,7 @@ module TidyJson
|
|
209
203
|
|
210
204
|
elsif !node.instance_of?(String) then graft << node.to_s
|
211
205
|
|
212
|
-
else graft << "\"#{node.gsub(
|
206
|
+
else graft << "\"#{node.gsub(/"/, '\\"')}\""
|
213
207
|
end
|
214
208
|
|
215
209
|
graft.strip
|
@@ -222,7 +216,7 @@ module TidyJson
|
|
222
216
|
# @param node [String] A serialized object member.
|
223
217
|
# @return [String] A copy of +node+ without a trailing comma.
|
224
218
|
def trim(node)
|
225
|
-
if (extra_comma = /(?<trail>,\s*[\]
|
219
|
+
if (extra_comma = /(?<trail>,\s*[\]}]\s*)$/.match(node))
|
226
220
|
node.sub(extra_comma[:trail],
|
227
221
|
extra_comma[:trail]
|
228
222
|
.slice(1, node.length.pred)
|
data/lib/tidy_json/serializer.rb
CHANGED
@@ -7,15 +7,93 @@ module TidyJson
|
|
7
7
|
# @api private
|
8
8
|
class Serializer
|
9
9
|
##
|
10
|
-
# Searches +obj+
|
11
|
-
#
|
10
|
+
# Searches +obj+ for readable attributes, storing them as key-value pairs in
|
11
|
+
# +json_hash+.
|
12
12
|
#
|
13
13
|
# @param obj [Object] A Ruby object that can be parsed as JSON.
|
14
14
|
# @param json_hash [{String,Symbol => #to_s}] Accumulator.
|
15
15
|
# @return [{String => #to_s}] A hash mapping of +obj+'s visible attributes.
|
16
|
+
# @note Hashes will be searched for nested objects to a maximum depth of 2;
|
17
|
+
# arrays to a maximum depth of 3.
|
18
|
+
# @example
|
19
|
+
# class Obj
|
20
|
+
# class Child
|
21
|
+
# def initialize; @a = { a: 1 } end
|
22
|
+
# attr_reader :a
|
23
|
+
# end
|
24
|
+
# def initialize; @a = { b: Child.new } end
|
25
|
+
# attr_accessor :a
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# o = Obj.new
|
29
|
+
# puts o.to_tidy_json
|
30
|
+
# <<JSON
|
31
|
+
# {
|
32
|
+
# "class": "Obj",
|
33
|
+
# "a": {
|
34
|
+
# "b": {
|
35
|
+
# "class": "Obj::Child",
|
36
|
+
# "a": {
|
37
|
+
# "a": 1
|
38
|
+
# }
|
39
|
+
# }
|
40
|
+
# }
|
41
|
+
# }
|
42
|
+
# JSON
|
43
|
+
#
|
44
|
+
# # depth > 2: unreachable objects are not serialized
|
45
|
+
# o.a = { b: { c: { d: Obj::Child.new } } }
|
46
|
+
# puts o.to_tidy_json
|
47
|
+
# <<JSON
|
48
|
+
# {
|
49
|
+
# "class": "Obj",
|
50
|
+
# "a": {
|
51
|
+
# "b": {
|
52
|
+
# "c": {
|
53
|
+
# "d": "#<Obj::Child:0x0000559d4da865c0>"
|
54
|
+
# }
|
55
|
+
# }
|
56
|
+
# }
|
57
|
+
# }
|
58
|
+
# JSON
|
59
|
+
#
|
60
|
+
# # object arrays can be nested up to 3 levels deep
|
61
|
+
# o.a = [ [ Obj::Child.new, [ Obj::Child.new, [ Obj::Child.new ] ] ] ]
|
62
|
+
# puts o.to_tidy_json
|
63
|
+
# <<JSON
|
64
|
+
# {
|
65
|
+
# "class": "Obj",
|
66
|
+
# "a": [
|
67
|
+
# {
|
68
|
+
# "class": "Obj::Child",
|
69
|
+
# "a": {
|
70
|
+
# "a": 1
|
71
|
+
# }
|
72
|
+
# },
|
73
|
+
# [
|
74
|
+
# [
|
75
|
+
# {
|
76
|
+
# "class": "Obj::Child",
|
77
|
+
# "a": {
|
78
|
+
# "a": 1
|
79
|
+
# }
|
80
|
+
# },
|
81
|
+
# [
|
82
|
+
# {
|
83
|
+
# "class": "Obj::Child",
|
84
|
+
# "a": {
|
85
|
+
# "a": 1
|
86
|
+
# }
|
87
|
+
# }
|
88
|
+
# ]
|
89
|
+
# ]
|
90
|
+
# ]
|
91
|
+
# ]
|
92
|
+
# }
|
93
|
+
# JSON
|
16
94
|
def self.serialize(obj, json_hash)
|
17
95
|
obj.instance_variables.each do |m|
|
18
|
-
key = m.to_s[/[
|
96
|
+
key = m.to_s[/[^@]\w*/].to_sym
|
19
97
|
|
20
98
|
next unless key && !key.eql?('')
|
21
99
|
|
@@ -28,24 +106,10 @@ module TidyJson
|
|
28
106
|
begin
|
29
107
|
# process class members of Hash type
|
30
108
|
if val.instance_of?(Hash)
|
31
|
-
nested_key = ''
|
32
|
-
nested = nil
|
33
|
-
|
34
|
-
val.each.any? do |k, v|
|
35
|
-
unless v.instance_variables.empty?
|
36
|
-
nested_key = k
|
37
|
-
nested = v
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
109
|
json_hash[key] = val
|
42
110
|
|
43
|
-
|
44
|
-
|
45
|
-
nested.instance_variables.each do
|
46
|
-
json_hash[key][pos] = serialize(nested,
|
47
|
-
class: nested.class.name)
|
48
|
-
end
|
111
|
+
val.each.any? do |k, v|
|
112
|
+
json_hash[key][k.to_sym] = serialize(v, class: v.class.name) unless v.instance_variables.empty?
|
49
113
|
end
|
50
114
|
|
51
115
|
# process class members of Array type
|
@@ -75,6 +139,11 @@ module TidyJson
|
|
75
139
|
elsif e.respond_to?(:each)
|
76
140
|
temp = []
|
77
141
|
e.each do |el|
|
142
|
+
if el.respond_to?(:each)
|
143
|
+
el.each_with_index do |ell, idx|
|
144
|
+
el[idx] = serialize(ell, class: ell.class.name) unless ell.instance_variables.empty?
|
145
|
+
end
|
146
|
+
end
|
78
147
|
temp << if el.instance_variables.empty? then el
|
79
148
|
else JSON.parse(el.stringify)
|
80
149
|
end
|
data/lib/tidy_json/version.rb
CHANGED
data/test/codecov_runner.rb
CHANGED
@@ -7,8 +7,8 @@ if ENV['COVERAGE']
|
|
7
7
|
SimpleCov.command_name 'Unit Tests'
|
8
8
|
|
9
9
|
if ENV['CI']
|
10
|
-
require '
|
11
|
-
SimpleCov.formatter = SimpleCov::Formatter::
|
10
|
+
require 'simplecov-cobertura'
|
11
|
+
SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
|
12
12
|
end
|
13
13
|
rescue LoadError
|
14
14
|
warn "Can't locate coverage drivers! Try running: `bundle install` first."
|
data/test/test_tidy_json.rb
CHANGED
@@ -11,11 +11,11 @@ class Nested
|
|
11
11
|
attr_accessor :a, :b, :c, :d, :e
|
12
12
|
|
13
13
|
def initialize
|
14
|
-
@a = [[], [[5, 6, 7]], [1, 2, {
|
15
|
-
@b = {
|
16
|
-
@c = [[1, 2, 3, [4]], {
|
17
|
-
@d = [{}, [], [1, 2, 3, [4]], {
|
18
|
-
@e = [[1, 2, 3, [4]], {
|
14
|
+
@a = [[], [[5, 6, 7]], [1, 2, { three: 3, four: [5, 6] }, [4]], { inner: 1 }]
|
15
|
+
@b = { a: [5], b: [1, 2, 3, [4]], c: [5], d: { inner: 1 }, e: [6] }
|
16
|
+
@c = [[1, 2, 3, [4]], { inner: 1 }, {}]
|
17
|
+
@d = [{}, [], [1, 2, 3, [4]], { inner: 1 }, []]
|
18
|
+
@e = [[1, 2, 3, [4]], { inner: 1 }, {}]
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -25,8 +25,8 @@ class JsonableObject
|
|
25
25
|
def initialize
|
26
26
|
@h = { one: 'uno', two: 'dos', three: %w[eine zwei drei], cuatro: ['I', 'II', 'III', ['i.', 'ii.', 'iii.', 'iv.']] }
|
27
27
|
@a = ['k', 'l', %w[M N O P], 'q', 'r', 's', [10, 456, ['<abbr title="Reel 2, Dialog Track 2">R2D2</abbr>', 'R', 2, 'D', ['two']]], 'u', 'v', 'x', 'y', %w[Z AB]]
|
28
|
-
@b = [{
|
29
|
-
@c = [[Nested.new, [Nested.new], [Nested.new]]]
|
28
|
+
@b = [{ uno: Nested.new }, [Nested.new, [[Nested.new], Nested.new, Nested.new], [Nested.new]]]
|
29
|
+
@c = [[Nested.new, [Nested.new, [Nested.new]], [Nested.new]]]
|
30
30
|
@d = []
|
31
31
|
@f = {}
|
32
32
|
end
|
@@ -64,13 +64,13 @@ class TidyJsonTest < Test::Unit::TestCase
|
|
64
64
|
assert_equal([{ a: 1 }, { b: 2 }, { c: 3, d: { a: 9, f: 56, i: '34', ii: '35' } }],
|
65
65
|
TidyJson.sort_keys(hash_array))
|
66
66
|
assert_equal({ a: 'one', b: 'two', c: 3 },
|
67
|
-
TidyJson.sort_keys(
|
67
|
+
TidyJson.sort_keys(b: 'two', c: 3, a: 'one'))
|
68
68
|
assert_equal([], TidyJson.sort_keys([]), 'return empty arrays unchanged')
|
69
69
|
assert_equal({}, TidyJson.sort_keys({}), 'return empty hashes unchanged')
|
70
70
|
assert_equal([3, 2, 1], TidyJson.sort_keys([3, 2, 1]),
|
71
71
|
'return arrays of keyless objects unchanged')
|
72
72
|
assert_equal([{ b: 'two' }, 'one'],
|
73
|
-
TidyJson.sort_keys([{
|
73
|
+
TidyJson.sort_keys([{ b: 'two' }, 'one']),
|
74
74
|
'arrays with any keyless objects should be returned unchanged')
|
75
75
|
end
|
76
76
|
|
@@ -82,25 +82,29 @@ class TidyJsonTest < Test::Unit::TestCase
|
|
82
82
|
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",
|
83
83
|
nested_hash_array.to_tidy_json(indent: 8, sort: true))
|
84
84
|
assert_equal("{\n \"a\": \"one\",\n \"b\": \"two\",\n \"c\": 3\n}\n",
|
85
|
-
{
|
85
|
+
{ b: 'two', c: 3, a: 'one' }.to_tidy_json(indent: 6, sort: true))
|
86
86
|
assert_equal("[]\n", [].to_tidy_json(sort: true))
|
87
87
|
assert_equal("{}\n", {}.to_tidy_json(sort: true))
|
88
88
|
assert_equal("[\n 3,\n 2,\n 1\n]\n",
|
89
89
|
[3, 2, 1].to_tidy_json(indent: 8, sort: true))
|
90
90
|
assert_equal("[\n {\n \"b\": \"two\"\n },\n \"one\"\n]\n",
|
91
|
-
[{
|
91
|
+
[{ b: 'two' }, 'one'].to_tidy_json(indent: 4, sort: true))
|
92
92
|
end
|
93
93
|
|
94
94
|
def test_tidy_instance
|
95
95
|
assert_equal({}.to_tidy_json, "{}\n")
|
96
96
|
assert_equal([].to_tidy_json, "[]\n")
|
97
97
|
assert_equal(String.new.to_tidy_json, "\"\"\n")
|
98
|
-
assert_equal(JsonableObject.new.to_tidy_json.length,
|
98
|
+
assert_equal(JsonableObject.new.to_tidy_json.length, 17_774)
|
99
99
|
end
|
100
100
|
|
101
101
|
def test_stringify_instance
|
102
|
-
File.
|
103
|
-
|
102
|
+
output = @t.write_json(File.join(__dir__, @t.class.name))
|
103
|
+
assert(File.exist?(output))
|
104
|
+
File.open(output, 'r:UTF-8') do |json|
|
105
|
+
text = json.read.strip
|
106
|
+
assert_equal(@t.stringify, text)
|
107
|
+
assert_no_match(/(#<Nested:0x)[a-z0-9]+>/, text, 'Some objects were not serialized!')
|
104
108
|
end
|
105
109
|
rescue Errno::ENOENT, Errno::EACCES, IOError => e
|
106
110
|
flunk "#{__FILE__}.#{__LINE__}: #{e.message}"
|
@@ -132,7 +136,7 @@ class TidyJsonTest < Test::Unit::TestCase
|
|
132
136
|
|
133
137
|
def test_indent_bounds_checking
|
134
138
|
assert_equal("{\n \"a\": \"one\",\n \"b\": \"two\",\n \"c\": 3\n}\n",
|
135
|
-
{
|
139
|
+
{ b: 'two', c: 3, a: 'one' }.to_tidy_json(indent: 5, sort: true),
|
136
140
|
'odd values should fall back to default of 2')
|
137
141
|
assert_equal([].to_tidy_json(indent: '16'), "[]\n",
|
138
142
|
'values > 12 should fall back to default of 2')
|