rubytree 2.2.2 → 2.2.3

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: ac435951d61ed62183f9f0ffcd194e3c357c440affeb298ac2058a70da0a3ea9
4
- data.tar.gz: 0eece8bb76eb39dd8625cfe04d84f975f185a554ea9b278724c6d89dc5d23c15
3
+ metadata.gz: 26db44620b76570b7b952dddc9eb36d2b1f0e885ba324953c9c8a9563eb47be6
4
+ data.tar.gz: c43b65542c8a14fe5f8230e00c041127451526dcabbe009485ea8e6f11489619
5
5
  SHA512:
6
- metadata.gz: bac2d9513820d824fed89417736042669fbb4bf8585c22ce95627c12260445519f8e832c027e2d1fa0c34706dd93dc43faec84000f543744182660d3f4500e19
7
- data.tar.gz: 1882283e8f675e6f44dc398b9132dabacd107b701eba272c2804dd31a77061eadfc92cd344affee5faa18363d141987da1dc385a74a802857d2a30003efb7268
6
+ metadata.gz: a2dff529a36b6bbae3ce9be8c05af13d68914860455ab40d4f9eeedb2b3031cc31f716637d19c38407bfdca2538ea06f6c5c17ab439ea07ff91c60ec99bf2722
7
+ data.tar.gz: 17657bdcbc22cc6bf7e48dfe621192c2d2137b2435e32fd013ecc8eaf7b5956dcf73b4cf99bf5942ac775dbb63d5fd4595cc72ea7f5477d3922848b022c608d4
data/Gemfile.lock CHANGED
@@ -1,23 +1,48 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rubytree (2.2.2)
5
- json (~> 2.0, >= 2.21.2)
4
+ rubytree (2.2.3)
5
+ json (>= 2.21.2, < 4)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- ast (2.4.2)
10
+ ast (2.4.3)
11
+ awesome_bot (1.20.0)
12
+ parallel (= 1.20.1)
13
+ bundler-audit (0.9.3)
14
+ bundler (>= 1.2.0)
15
+ thor (~> 1.0)
16
+ chef-utils (19.3.15)
17
+ concurrent-ruby
18
+ concurrent-ruby (1.3.8)
11
19
  date (3.4.1)
12
20
  diff-lcs (1.5.1)
13
21
  docile (1.4.1)
14
- json (2.21.2)
15
- language_server-protocol (3.17.0.3)
16
- parallel (1.26.3)
17
- parser (3.3.6.0)
22
+ json (3.0.2)
23
+ kramdown (2.5.2)
24
+ rexml (>= 3.4.4)
25
+ kramdown-parser-gfm (1.1.0)
26
+ kramdown (~> 2.0)
27
+ language_server-protocol (3.17.0.6)
28
+ lint_roller (1.1.0)
29
+ mdl (0.14.0)
30
+ kramdown (~> 2.3)
31
+ kramdown-parser-gfm (~> 1.1)
32
+ mixlib-cli (~> 2.1, >= 2.1.1)
33
+ mixlib-config (>= 2.2.1, < 4)
34
+ mixlib-shellout
35
+ mixlib-cli (2.1.8)
36
+ mixlib-config (3.0.27)
37
+ tomlrb
38
+ mixlib-shellout (3.3.8)
39
+ chef-utils
40
+ parallel (1.20.1)
41
+ parser (3.3.12.0)
18
42
  ast (~> 2.4.1)
19
43
  racc
20
44
  power_assert (2.0.4)
45
+ prism (1.9.0)
21
46
  psych (5.2.2)
22
47
  date
23
48
  stringio
@@ -26,7 +51,8 @@ GEM
26
51
  rake (13.2.1)
27
52
  rdoc (6.10.0)
28
53
  psych (>= 4.0.0)
29
- regexp_parser (2.9.3)
54
+ regexp_parser (2.13.1)
55
+ rexml (3.4.4)
30
56
  rspec (3.13.0)
31
57
  rspec-core (~> 3.13.0)
32
58
  rspec-expectations (~> 3.13.0)
@@ -43,22 +69,27 @@ GEM
43
69
  rtags (0.97)
44
70
  rtagstask (0.0.4)
45
71
  rtags (> 0.0.0)
46
- rubocop (1.69.2)
47
- json (~> 2.3)
48
- language_server-protocol (>= 3.17.0)
49
- parallel (~> 1.10)
72
+ rubocop (1.91.0)
73
+ json (>= 2.3)
74
+ language_server-protocol (~> 3.17.0.2)
75
+ lint_roller (~> 1.1.0)
76
+ parallel (>= 1.10)
50
77
  parser (>= 3.3.0.2)
51
78
  rainbow (>= 2.2.2, < 4.0)
52
79
  regexp_parser (>= 2.9.3, < 3.0)
53
- rubocop-ast (>= 1.36.2, < 2.0)
80
+ rubocop-ast (>= 1.49.0, < 2.0)
54
81
  ruby-progressbar (~> 1.7)
55
82
  unicode-display_width (>= 2.4.0, < 4.0)
56
- rubocop-ast (1.37.0)
57
- parser (>= 3.3.1.0)
58
- rubocop-rake (0.6.0)
59
- rubocop (~> 1.0)
60
- rubocop-rspec (3.3.0)
61
- rubocop (~> 1.61)
83
+ rubocop-ast (1.50.0)
84
+ parser (>= 3.3.7.2)
85
+ prism (~> 1.7)
86
+ rubocop-rake (0.7.1)
87
+ lint_roller (~> 1.1)
88
+ rubocop (>= 1.72.1)
89
+ rubocop-rspec (3.10.2)
90
+ lint_roller (~> 1.1)
91
+ regexp_parser (>= 2.0)
92
+ rubocop (~> 1.86, >= 1.86.2)
62
93
  ruby-progressbar (1.13.0)
63
94
  simplecov (0.22.0)
64
95
  docile (~> 1.1)
@@ -70,21 +101,25 @@ GEM
70
101
  stringio (3.1.2)
71
102
  test-unit (3.6.7)
72
103
  power_assert
73
- unicode-display_width (3.1.2)
74
- unicode-emoji (~> 4.0, >= 4.0.4)
75
- unicode-emoji (4.0.4)
104
+ thor (1.5.0)
105
+ tomlrb (2.0.4)
106
+ unicode-display_width (3.3.0)
107
+ unicode-emoji (~> 4.3)
108
+ unicode-emoji (4.3.0)
76
109
  yard (0.9.44)
77
110
 
78
111
  PLATFORMS
79
112
  ruby
80
113
 
81
114
  DEPENDENCIES
82
- bundler (~> 2.3)
115
+ awesome_bot (~> 1.20)
116
+ bundler-audit (~> 0.9)
117
+ mdl (~> 0.14)
83
118
  rake (~> 13.2)
84
119
  rdoc (~> 6.10)
85
120
  rspec (~> 3.0, >= 3.13)
86
121
  rtagstask (~> 0.0.4)
87
- rubocop (~> 1.69)
122
+ rubocop (~> 1.90)
88
123
  rubocop-rake (~> 0.6)
89
124
  rubocop-rspec (~> 3.3)
90
125
  rubytree!
data/History.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # History of Changes
2
2
 
3
+ ### 2.2.3 / 2026-10-07
4
+
5
+ * Add support for the `json` gem 3.x while preserving compatibility with 2.x.
6
+
7
+ * Add `Tree::TreeNode.from_json` to rebuild trees from JSON documents.
8
+
3
9
  ### 2.2.2 / 2026-08-09
4
10
 
5
11
  * Bump the `json` runtime dependency to 2.21.2+, fixing a use-after-free in
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # **RubyTree** #
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/rubytree.png)](http://badge.fury.io/rb/rubytree)
4
+ [![Gem Downloads](https://img.shields.io/gem/dt/rubytree)](https://rubygems.org/gems/rubytree)
4
5
  ![Build State](https://github.com/evolve75/rubytree/actions/workflows/ruby.yml/badge.svg)
5
- [![Code Climate](https://codeclimate.com/github/evolve75/RubyTree.png)](https://codeclimate.com/github/evolve75/RubyTree)
6
6
  [![Coverage Status](https://coveralls.io/repos/evolve75/RubyTree/badge.png)](https://coveralls.io/r/evolve75/RubyTree)
7
7
 
8
8
  __ _ _
@@ -55,7 +55,7 @@ See the [API][rt_doc] documentation for more details.
55
55
  #
56
56
  # example_basic.rb:: Basic usage of the tree library.
57
57
  #
58
- # Copyright (C) 2013-2022 Anupam Sengupta <anupamsg@gmail.com>
58
+ # Copyright (C) 2013-2026 Anupam Sengupta <anupamsg@gmail.com>
59
59
  #
60
60
  # The following example implements this tree structure:
61
61
  #
@@ -123,7 +123,7 @@ This example can also be found at
123
123
 
124
124
  * Run-time Dependencies:
125
125
 
126
- * [JSON][] for converting to/from the JSON format
126
+ * [JSON][] (2.x or 3.x) for converting to/from the JSON format
127
127
 
128
128
 
129
129
  * Development dependencies (not required for installing the gem):
@@ -155,7 +155,7 @@ command at the terminal to view the text mode ri documentation:
155
155
 
156
156
  Documentation for the latest released version is available at:
157
157
 
158
- [rubytree.anupamsg.me/rdoc][rt_doc]
158
+ [rubytree.anupamsg.me/doc][rt_doc]
159
159
 
160
160
  Documentation for the latest git HEAD is available at:
161
161
 
@@ -259,7 +259,7 @@ A big thanks to the following contributors for helping improve **RubyTree**:
259
259
  [pre-order]: http://en.wikipedia.org/wiki/Tree_traversal#Pre-order "Pre-ordered Traversal"
260
260
  [rt@github]: http://github.com/evolve75/RubyTree "RubyTree Project Page on Github"
261
261
  [rt_doc@head]: http://rdoc.info/projects/evolve75/RubyTree "RubyTree Documentation for VCS Head"
262
- [rt_doc]: http://rubytree.anupamsg.me/rdoc "RubyTree Documentation"
262
+ [rt_doc]: https://rubytree.anupamsg.me/doc/ "RubyTree Documentation"
263
263
  [rt_gem]: http://rubygems.org/gems/rubytree "RubyTree Gem"
264
264
  [rt_site]: http://rubytree.anupamsg.me "RubyTree Site"
265
265
  [tree_data_structure]: http://en.wikipedia.org/wiki/Tree_data_structure "Tree Data Structure"
data/Rakefile CHANGED
@@ -42,6 +42,9 @@ GEM_SPEC = Bundler.load_gemspec(File.join(__dir__, 'rubytree.gemspec'))
42
42
  PKG_NAME = GEM_SPEC.name
43
43
  PKG_VER = GEM_SPEC.version
44
44
  GEM_NAME = "#{PKG_NAME}-#{PKG_VER}.gem"
45
+ MARKDOWN_FILES = Dir['**/*.md'].reject do |path|
46
+ path.start_with?('vendor/', 'pkg/')
47
+ end.sort
45
48
 
46
49
  desc 'Default Task (Run the tests)'
47
50
  task :default do
@@ -98,6 +101,19 @@ namespace :doc do # ................................ Documentation
98
101
  task :clobber_yard do
99
102
  rm_rf 'doc'
100
103
  end
104
+
105
+ desc 'Run markdown lint checks'
106
+ task :lint do
107
+ sh('mdl', '--config', '.mdlrc', *MARKDOWN_FILES)
108
+ end
109
+
110
+ desc 'Validate http(s) links in markdown files'
111
+ task :links do
112
+ sh('awesome_bot', *MARKDOWN_FILES, '--allow-redirect', '--allow-dupe')
113
+ end
114
+
115
+ desc 'Run markdown lint and link checks'
116
+ task check: %i[lint links]
101
117
  end
102
118
 
103
119
  desc 'Run the unit tests'
@@ -178,6 +194,33 @@ require 'rubocop/rake_task'
178
194
 
179
195
  RuboCop::RakeTask.new(:rubocop) do |t|
180
196
  t.options = ['--display-cop-names']
181
- t.requires << 'rubocop-rake'
182
- t.requires << 'rubocop-rspec'
197
+ end
198
+
199
+ # ................................ Gem metadata
200
+ desc 'Validate gemspec metadata and required fields'
201
+ task :gemspec do
202
+ GEM_SPEC.validate
203
+ puts 'Gemspec is valid.'
204
+ end
205
+
206
+ # ................................ Linting
207
+ desc 'Run lint checks'
208
+ task lint: %i[gemspec rubocop]
209
+
210
+ # ................................ Security checks
211
+ desc 'Run security checks (bundler-audit, semgrep)'
212
+ task :security do
213
+ sh('bundle', 'exec', 'bundler-audit', 'check', '--update')
214
+
215
+ semgrep_available = system('command -v semgrep >/dev/null 2>&1')
216
+ unless semgrep_available
217
+ warn 'WARN: semgrep not found; skipping semgrep security scan.'
218
+ return
219
+ end
220
+
221
+ env = {}
222
+ default_cert = '/etc/ssl/cert.pem'
223
+ env['SSL_CERT_FILE'] = default_cert if ENV['SSL_CERT_FILE'].nil? && File.exist?(default_cert)
224
+
225
+ sh(env, 'semgrep', '--config', 'p/r2c-security-audit', '--config', 'p/ruby', 'lib')
183
226
  end
@@ -4,7 +4,7 @@
4
4
  #
5
5
  # Author: Anupam Sengupta
6
6
  # Time-stamp: <2022-06-19 22:52:29 anupam>
7
- # Copyright (C) 2013, 2015, 2022 Anupam Sengupta <anupamsg@gmail.com>
7
+ # Copyright (C) 2013-2026 Anupam Sengupta <anupamsg@gmail.com>
8
8
  #
9
9
  # The following example implements this tree structure:
10
10
  #
@@ -43,7 +43,21 @@ module Tree
43
43
  module Utils
44
44
  # Provides utility methods to convert a {Tree::TreeNode} to and from
45
45
  # JSON[http://flori.github.com/json/].
46
+ #
47
+ # Every serialized node carries its Ruby class name under the +json_class+
48
+ # key. With the json gem 2.x this is the key the gem inspects itself when
49
+ # parsing with +create_additions: true+. The json gem 3.x removed that
50
+ # option together with the +JSON.create_id+ accessor, so RubyTree emits the
51
+ # key on its own and rebuilds the nodes through {ClassMethods#from_json},
52
+ # which relies on the +on_load+ parser callback available in both the json
53
+ # gem 2.x and 3.x.
46
54
  module JSONConverter
55
+ # The hash key which carries the Ruby class name of a serialized node.
56
+ # Equals the default +JSON.create_id+ of the json gem 2.x, so documents
57
+ # written by earlier RubyTree versions stay readable.
58
+ JSON_CLASS_KEY = 'json_class'
59
+
60
+ # Extend the base class with the converter class methods.
47
61
  def self.included(base)
48
62
  base.extend(ClassMethods)
49
63
  end
@@ -69,7 +83,7 @@ module Tree
69
83
  json_hash = {
70
84
  name: name,
71
85
  content: content,
72
- JSON.create_id => self.class.name
86
+ JSON_CLASS_KEY => self.class.name
73
87
  }
74
88
 
75
89
  json_hash['children'] = children if children?
@@ -86,7 +100,7 @@ module Tree
86
100
  #
87
101
  # @return The JSON representation of this subtree.
88
102
  #
89
- # @see ClassMethods#json_create
103
+ # @see ClassMethods#from_json
90
104
  # @see #as_json
91
105
  # @see http://flori.github.com/json
92
106
  def to_json(*args)
@@ -96,17 +110,93 @@ module Tree
96
110
  # ClassMethods for the {JSONConverter} module. Will become class methods in
97
111
  # the +include+ target.
98
112
  module ClassMethods
113
+ # Parses a JSON document and returns the tree it describes.
114
+ #
115
+ # tree = Tree::TreeNode.from_json(json_string)
116
+ #
117
+ # The nodes are rebuilt from the hashes tagged with the +json_class+
118
+ # key, see {#json_on_load} for the rules. The document root must
119
+ # describe a node of this class, or of one of its subclasses.
120
+ #
121
+ # This works with both the json gem 2.x and 3.x. With the json gem 2.x
122
+ # the legacy +JSON.parse(json_string, create_additions: true)+ keeps
123
+ # working as well, but the json gem 3.x removed that option.
124
+ #
125
+ # @param [String] source The JSON document to parse.
126
+ #
127
+ # @return [Tree::TreeNode] The root node of the parsed tree.
128
+ #
129
+ # @raise [ArgumentError] This exception is raised if the document does
130
+ # not describe a node of this class.
131
+ #
132
+ # @raise [JSON::ParserError] This exception is raised if the document
133
+ # is not valid JSON.
134
+ #
135
+ # @see #json_on_load
136
+ # @see JSONConverter#to_json
137
+ def from_json(source)
138
+ tree = JSON.parse(source, on_load: method(:json_on_load).to_proc)
139
+ return tree if tree.is_a?(self)
140
+
141
+ raise ArgumentError, "JSON document does not describe a #{self} tree"
142
+ end
143
+
144
+ # Rebuilds a node from its parsed JSON hash. This is the +on_load+
145
+ # callback for the parser of the json gem, used by {#from_json} and
146
+ # available for custom parser setups (the parser expects a +Proc+):
147
+ #
148
+ # on_load = Tree::TreeNode.method(:json_on_load).to_proc
149
+ # tree = JSON::Coder.new(on_load: on_load).load(json_string)
150
+ #
151
+ # The parser invokes the callback for every parsed value, innermost
152
+ # first, so the +children+ of a node hash are already nodes by the
153
+ # time the hash of their parent arrives here. Only hashes whose
154
+ # +json_class+ names this class or one of its subclasses are turned
155
+ # into nodes, any other value is returned untouched. This keeps the
156
+ # deserialization scoped to tree nodes, unlike the global
157
+ # +create_additions+ mechanism of the json gem 2.x which instantiated
158
+ # any class named in a document.
159
+ #
160
+ # @param [Object] object A value produced by the JSON parser.
161
+ #
162
+ # @return [Tree::TreeNode, Object] The node for a tagged hash, or the
163
+ # unchanged value otherwise.
164
+ #
165
+ # @see #from_json
166
+ # @see #json_create
167
+ def json_on_load(object)
168
+ return object unless object.is_a?(Hash)
169
+
170
+ node_class = json_node_class(object[JSON_CLASS_KEY])
171
+ node_class ? node_class.json_create(object) : object
172
+ end
173
+
174
+ # Resolves the class named by a +json_class+ tag, limited to this
175
+ # class and its subclasses.
176
+ #
177
+ # @param [String, nil] class_name The class name found in the tag.
178
+ #
179
+ # @return [Class, nil] The node class, or +nil+ when the tag is
180
+ # missing, unknown or names a foreign class.
181
+ def json_node_class(class_name)
182
+ return nil unless class_name.is_a?(String)
183
+
184
+ node_class = Object.const_get(class_name)
185
+ node_class if node_class.is_a?(Class) && node_class <= self
186
+ rescue NameError
187
+ nil
188
+ end
189
+
99
190
  # Helper method to create a Tree::TreeNode instance from the JSON hash
100
191
  # representation. Note that this method should *NOT* be called directly.
101
- # Instead, to convert the JSON hash back to a tree, do:
102
- #
103
- # tree = JSON.parse(the_json_hash, create_additions: true)
192
+ # Instead, to convert a JSON document back to a tree, do:
104
193
  #
105
- # This operation requires the {JSON gem}[http://flori.github.com/json/] to
106
- # be available, or else the operation fails with a warning message.
194
+ # tree = Tree::TreeNode.from_json(the_json_string)
107
195
  #
108
- # Note the +create_additions: true+ option, which is *required* for
109
- # successfully parsing the string or hash.
196
+ # With the json gem 2.x this method also serves as the hook of the
197
+ # +create_additions+ mechanism, so the legacy
198
+ # +JSON.parse(the_json_string, create_additions: true)+ keeps working
199
+ # there. The json gem 3.x removed that mechanism.
110
200
  #
111
201
  # @author Dirk Breuer (http://github.com/railsbros-dirk)
112
202
  # @since 0.7.0
@@ -115,8 +205,9 @@ module Tree
115
205
  #
116
206
  # @return [Tree::TreeNode] The created tree.
117
207
  #
118
- # @see #to_json
119
- # @see http://flori.github.com/json
208
+ # @see #from_json
209
+ # @see #json_on_load
210
+ # @see JSONConverter#to_json
120
211
  def json_create(json_hash)
121
212
  node = new(json_hash['name'], json_hash['content'])
122
213
 
data/lib/tree/version.rb CHANGED
@@ -35,5 +35,5 @@
35
35
 
36
36
  module Tree
37
37
  # Rubytree Package Version
38
- VERSION = '2.2.2'
38
+ VERSION = '2.2.3'
39
39
  end
@@ -196,6 +196,19 @@ module TestTree
196
196
  assert_equal(Enumerable::Enumerator, f.inordered_each.class) if defined?(Enumerable::Enumerator.class)
197
197
  end
198
198
 
199
+ # Test the JSON round trip, which has to rebuild binary tree nodes.
200
+ def test_json_round_trip
201
+ @root << @left_child1 << Tree::BinaryTreeNode.new('C', 'Grand child')
202
+ @root << @right_child1
203
+
204
+ tree = Tree::BinaryTreeNode.from_json(@root.to_json)
205
+
206
+ assert_equal(Tree::BinaryTreeNode, tree.class)
207
+ assert_equal(@left_child1.name, tree.left_child.name)
208
+ assert_equal(@right_child1.name, tree.right_child.name)
209
+ assert_equal('C', tree.left_child.left_child.name)
210
+ end
211
+
199
212
  # Test the left_child method.
200
213
  def test_left_child
201
214
  @root << @left_child1
@@ -48,6 +48,22 @@ module TestTree
48
48
  root = MyNode.new('Root')
49
49
  assert_equal(MyNode, root.detached_copy.class)
50
50
  end
51
+
52
+ def test_from_json_same_clz
53
+ root = MyNode.new('Root')
54
+ root << MyNode.new('Child')
55
+
56
+ tree = MyNode.from_json(root.to_json)
57
+
58
+ assert_equal(MyNode, tree.class)
59
+ assert_equal(MyNode, tree['Child'].class)
60
+ end
61
+
62
+ def test_from_json_via_base_class
63
+ tree = Tree::TreeNode.from_json(MyNode.new('Root').to_json)
64
+
65
+ assert_equal(MyNode, tree.class)
66
+ end
51
67
  end
52
68
  end
53
69
 
data/test/test_tree.rb CHANGED
@@ -1345,16 +1345,16 @@ module TestTree
1345
1345
  expected_json = {
1346
1346
  'name' => 'ROOT',
1347
1347
  'content' => 'Root Node',
1348
- JSON.create_id => 'Tree::TreeNode',
1348
+ 'json_class' => 'Tree::TreeNode',
1349
1349
  'children' => [
1350
- { 'name' => 'Child1', 'content' => 'Child Node 1', JSON.create_id => 'Tree::TreeNode' },
1351
- { 'name' => 'Child2', 'content' => 'Child Node 2', JSON.create_id => 'Tree::TreeNode' },
1350
+ { 'name' => 'Child1', 'content' => 'Child Node 1', 'json_class' => 'Tree::TreeNode' },
1351
+ { 'name' => 'Child2', 'content' => 'Child Node 2', 'json_class' => 'Tree::TreeNode' },
1352
1352
  {
1353
1353
  'name' => 'Child3',
1354
1354
  'content' => 'Child Node 3',
1355
- JSON.create_id => 'Tree::TreeNode',
1355
+ 'json_class' => 'Tree::TreeNode',
1356
1356
  'children' => [
1357
- { 'name' => 'Child4', 'content' => 'Grand Child 1', JSON.create_id => 'Tree::TreeNode' }
1357
+ { 'name' => 'Child4', 'content' => 'Grand Child 1', 'json_class' => 'Tree::TreeNode' }
1358
1358
  ]
1359
1359
  }
1360
1360
  ]
@@ -1367,22 +1367,22 @@ module TestTree
1367
1367
  tree_as_json = {
1368
1368
  'name' => 'ROOT',
1369
1369
  'content' => 'Root Node',
1370
- JSON.create_id => 'Tree::TreeNode',
1370
+ 'json_class' => 'Tree::TreeNode',
1371
1371
  'children' => [
1372
- { 'name' => 'Child1', 'content' => 'Child Node 1', JSON.create_id => 'Tree::TreeNode' },
1373
- { 'name' => 'Child2', 'content' => 'Child Node 2', JSON.create_id => 'Tree::TreeNode' },
1372
+ { 'name' => 'Child1', 'content' => 'Child Node 1', 'json_class' => 'Tree::TreeNode' },
1373
+ { 'name' => 'Child2', 'content' => 'Child Node 2', 'json_class' => 'Tree::TreeNode' },
1374
1374
  {
1375
1375
  'name' => 'Child3',
1376
1376
  'content' => 'Child Node 3',
1377
- JSON.create_id => 'Tree::TreeNode',
1377
+ 'json_class' => 'Tree::TreeNode',
1378
1378
  'children' => [
1379
- { 'name' => 'Child4', 'content' => 'Grand Child 1', JSON.create_id => 'Tree::TreeNode' }
1379
+ { 'name' => 'Child4', 'content' => 'Grand Child 1', 'json_class' => 'Tree::TreeNode' }
1380
1380
  ]
1381
1381
  }
1382
1382
  ]
1383
1383
  }.to_json
1384
1384
 
1385
- tree = JSON.parse(tree_as_json, create_additions: true)
1385
+ tree = Tree::TreeNode.from_json(tree_as_json)
1386
1386
 
1387
1387
  assert_equal(@root.name, tree.root.name, 'Root should be returned')
1388
1388
  assert_equal(@child1.name, tree[0].name, 'Child 1 should be returned')
@@ -1399,7 +1399,7 @@ module TestTree
1399
1399
 
1400
1400
  j = root_node.to_json
1401
1401
 
1402
- k = JSON.parse(j, create_additions: true)
1402
+ k = Tree::TreeNode.from_json(j)
1403
1403
 
1404
1404
  assert_equal(k.name, root_node.name, 'Root should be returned')
1405
1405
  assert_equal(k[0].name, root_node[0].name, 'Child 1 should be returned')
@@ -1407,6 +1407,34 @@ module TestTree
1407
1407
  assert_equal(k[1].name, root_node[1].name, 'Child 2 should be returned')
1408
1408
  end
1409
1409
 
1410
+ # The json gem 2.x can still rebuild the tree on its own through its
1411
+ # +create_additions+ mechanism, which the json gem 3.x removed.
1412
+ def test_json_create_additions_on_json2
1413
+ omit_unless(JSON.respond_to?(:create_id),
1414
+ 'create_additions was removed by the json gem 3.x')
1415
+ setup_test_tree
1416
+
1417
+ tree = JSON.parse(@root.to_json, create_additions: true)
1418
+
1419
+ assert_equal(@root.name, tree.name, 'Root should be returned')
1420
+ assert_equal(@child4.name, tree[2][0].name,
1421
+ 'Grand Child 1 should be returned')
1422
+ end
1423
+
1424
+ def test_from_json_rejects_foreign_class
1425
+ document = { 'json_class' => 'String', 'raw' => [112, 119, 110] }.to_json
1426
+
1427
+ assert_raise(ArgumentError) { Tree::TreeNode.from_json(document) }
1428
+ end
1429
+
1430
+ def test_from_json_keeps_untagged_content
1431
+ root_node = Tree::TreeNode.new('ROOT', { 'key' => 'value' })
1432
+
1433
+ tree = Tree::TreeNode.from_json(root_node.to_json)
1434
+
1435
+ assert_equal({ 'key' => 'value' }, tree.content)
1436
+ end
1437
+
1410
1438
  # Test usage of integers as node names
1411
1439
  def test_integer_node_names
1412
1440
  @n_root = Tree::TreeNode.new(0, 'Root Node')
metadata CHANGED
@@ -1,49 +1,77 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubytree
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.2
4
+ version: 2.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anupam Sengupta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-09 00:00:00.000000000 Z
11
+ date: 2026-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '2.0'
20
17
  - - ">="
21
18
  - !ruby/object:Gem::Version
22
19
  version: 2.21.2
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '4'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - "~>"
28
- - !ruby/object:Gem::Version
29
- version: '2.0'
30
27
  - - ">="
31
28
  - !ruby/object:Gem::Version
32
29
  version: 2.21.2
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '4'
33
+ - !ruby/object:Gem::Dependency
34
+ name: awesome_bot
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.20'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.20'
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler-audit
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.9'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.9'
33
61
  - !ruby/object:Gem::Dependency
34
- name: bundler
62
+ name: mdl
35
63
  requirement: !ruby/object:Gem::Requirement
36
64
  requirements:
37
65
  - - "~>"
38
66
  - !ruby/object:Gem::Version
39
- version: '2.3'
67
+ version: '0.14'
40
68
  type: :development
41
69
  prerelease: false
42
70
  version_requirements: !ruby/object:Gem::Requirement
43
71
  requirements:
44
72
  - - "~>"
45
73
  - !ruby/object:Gem::Version
46
- version: '2.3'
74
+ version: '0.14'
47
75
  - !ruby/object:Gem::Dependency
48
76
  name: rake
49
77
  requirement: !ruby/object:Gem::Requirement
@@ -112,14 +140,14 @@ dependencies:
112
140
  requirements:
113
141
  - - "~>"
114
142
  - !ruby/object:Gem::Version
115
- version: '1.69'
143
+ version: '1.90'
116
144
  type: :development
117
145
  prerelease: false
118
146
  version_requirements: !ruby/object:Gem::Requirement
119
147
  requirements:
120
148
  - - "~>"
121
149
  - !ruby/object:Gem::Version
122
- version: '1.69'
150
+ version: '1.90'
123
151
  - !ruby/object:Gem::Dependency
124
152
  name: rubocop-rake
125
153
  requirement: !ruby/object:Gem::Requirement
@@ -304,7 +332,7 @@ post_install_message: |2
304
332
  ========================================================================
305
333
  rdoc_options:
306
334
  - "--title"
307
- - 'Rubytree Documentation: rubytree-2.2.2'
335
+ - 'Rubytree Documentation: rubytree-2.2.3'
308
336
  - "--main"
309
337
  - README.md
310
338
  - "--quiet"