tidy_json 0.3.0 → 0.5.1

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: 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')