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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 63e286aa3b914d96b71dc8e32904e0ca0514deb1159bf9a8ef40978f7b2a2ea0
4
- data.tar.gz: f7f30ca7fab2e95b93b4aa385d5e2a4752899b273a160e8a64151c68248fb0d5
3
+ metadata.gz: 3074ee12d1a54599a8c9160fc353c5b7969655296769ddf8c43a6ed0a731af77
4
+ data.tar.gz: 8c5b373ec10867f9b2b3c66c8ee1e14e7973cdd7e21fc0c1de6e832affebf600
5
5
  SHA512:
6
- metadata.gz: 7a55fad57a8ffd7d303bb19cbd2f89665faad9089650d7324809fb7c4f1553c27b7783d6e608d0c284b3e91617801ae100a391bb11d142d023d3f037914ed8a0
7
- data.tar.gz: 612e14c4480664a0ddb64cc8caac22915a60ca3709e3b1f5cae974eae050333104c5066ba513f70c1c5686dbc096f8c2663853126d4df2bd74d62860d4375ee3
6
+ metadata.gz: fe1e81b90fb55da164698cb56b45232b64d1fcea274beb64f0ea1762e5eea0728904a3c487172d6bf266755b9f962126277d54b185ea6baf81b7029769bcc894
7
+ data.tar.gz: 0a84a04ba5440f12df61b45cbdc86cef16ce5c3dd2b866eacf5249999873e0c204344c20488c3857fd0aa288fedb388e5c195d2aed8b4c1aa53e52f105df362a
data/.yardopts CHANGED
@@ -1,3 +1,2 @@
1
- --exclude lib/tidy_json/dedication.rb
2
1
  --private lib/**/*.rb
3
2
  --files README.md,LICENSE
data/Gemfile CHANGED
@@ -7,3 +7,10 @@ gemspec
7
7
  group :test do
8
8
  gem 'rake'
9
9
  end
10
+
11
+ group :development do
12
+ install_if -> { ENV['COVERAGE'] } do
13
+ gem 'simplecov'
14
+ gem 'simplecov-cobertura'
15
+ end
16
+ end
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2019-2021 Robert Di Pardo
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] [![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] [![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
- [cci_build_status]: https://circleci.com/gh/rdipardo/tidy_json/tree/master
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://github.com/rdipardo/tidy_json/blob/master/LICENSE
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
@@ -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) An even number of spaces to
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 [String] :array_nl ("\n") A string to put at the end of
26
- # each array member.
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+ should be generated. If +false+, an
36
- # exception is thrown if these values are encountered.
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 key.
39
- # @see https://github.com/flori/json/blob/d49c5de49e54a5ad3f6fcf587f98d63266ef9439/lib/json/pure/generator.rb#L111 JSON::Pure::Generator
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 = (2..8).include?(opts[:space_before])
50
- valid_space_after = (2..8).include?(opts[:space])
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] || "\n",
60
- array_nl: opts[:array_nl] || "\n",
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.length > 1 &&
83
- (obj.instance_of?(Array) &&
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 = if h.first.eql? ''
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*[\]\}]\s*)$/.match(node))
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)
@@ -7,15 +7,93 @@ module TidyJson
7
7
  # @api private
8
8
  class Serializer
9
9
  ##
10
- # Searches +obj+ to a maximum depth of 2 for readable attributes, storing
11
- # them as key-value pairs in +json_hash+.
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[/[^\@]\w*/].to_sym
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
- if nested
44
- pos = val.keys.select { |k| k === nested_key }.first.to_sym
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TidyJson
4
- VERSION = '0.3.0'
4
+ VERSION = '0.5.1'
5
5
  end
@@ -7,8 +7,8 @@ if ENV['COVERAGE']
7
7
  SimpleCov.command_name 'Unit Tests'
8
8
 
9
9
  if ENV['CI']
10
- require 'codecov'
11
- SimpleCov.formatter = SimpleCov::Formatter::Codecov
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."
@@ -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, { '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 }, {}]
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 = [{ 'uno': Nested.new }, [Nested.new, [Nested.new, Nested.new], [Nested.new]]]
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('b': 'two', 'c': 3, 'a': 'one'))
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([{ 'b': 'two' }, 'one']),
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
- { 'b': 'two', 'c': 3, 'a': 'one' }.to_tidy_json(indent: 6, sort: true))
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
- [{ 'b': 'two' }, 'one'].to_tidy_json(indent: 4, sort: true))
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, 13_410)
98
+ assert_equal(JsonableObject.new.to_tidy_json.length, 17_774)
99
99
  end
100
100
 
101
101
  def test_stringify_instance
102
- File.open("#{__dir__}/JsonableObject.json", 'r') do |json|
103
- assert_equal(@t.stringify, json.read.strip)
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
- { 'b': 'two', 'c': 3, 'a': 'one' }.to_tidy_json(indent: 5, sort: true),
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')