lutaml-model 0.8.129 → 0.8.131
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/docs/key_value_data_model.adoc +9 -9
- data/lib/lutaml/json/adapter/multi_json_adapter.rb +1 -7
- data/lib/lutaml/json/adapter/oj_adapter.rb +1 -7
- data/lib/lutaml/json/adapter/standard_adapter.rb +1 -7
- data/lib/lutaml/key_value/adapter/json/multi_json_adapter.rb +1 -7
- data/lib/lutaml/key_value/adapter/json/oj_adapter.rb +1 -7
- data/lib/lutaml/key_value/adapter/json/standard_adapter.rb +1 -7
- data/lib/lutaml/key_value/adapter/toml/teptris_adapter.rb +1 -7
- data/lib/lutaml/key_value/adapter/toml/toml_rb_adapter.rb +1 -7
- data/lib/lutaml/key_value/adapter/toml/tomlib_adapter.rb +1 -7
- data/lib/lutaml/key_value/adapter/yaml/standard_adapter.rb +1 -7
- data/lib/lutaml/key_value/data_model/element.rb +31 -32
- data/lib/lutaml/key_value/mapping_rule.rb +4 -4
- data/lib/lutaml/key_value/transform.rb +6 -5
- data/lib/lutaml/key_value/transformation.rb +21 -18
- data/lib/lutaml/model/attribute.rb +23 -1
- data/lib/lutaml/model/collection.rb +1 -5
- data/lib/lutaml/model/mapping/mapping_rule.rb +21 -2
- data/lib/lutaml/model/schema/xml_compiler/spec_builder/complex_types.rb +9 -4
- data/lib/lutaml/model/schema/xml_compiler/spec_builder/members.rb +15 -8
- data/lib/lutaml/model/schema/xml_compiler/spec_builder.rb +2 -2
- data/lib/lutaml/model/schema/xml_compiler/supported_data_types.rb +10 -0
- data/lib/lutaml/model/serialize/attribute_definition.rb +133 -2
- data/lib/lutaml/model/serialize/initialization.rb +12 -0
- data/lib/lutaml/model/serialize.rb +30 -10
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/toml/adapter/toml_rb_adapter.rb +1 -7
- data/lib/lutaml/toml/adapter/tomlib_adapter.rb +1 -7
- data/lib/lutaml/xml/builder/base.rb +3 -1
- data/lib/lutaml/xml/transformation/rule_compiler.rb +2 -2
- data/lib/lutaml/yaml/adapter/standard_adapter.rb +1 -7
- data/lib/lutaml/yaml/adapter/yeptris_adapter.rb +1 -7
- data/lib/lutaml/yamlld/adapter.rb +1 -6
- data/spec/lutaml/key_value/adapter/toml/teptris_adapter_spec.rb +3 -3
- data/spec/lutaml/key_value/group_instantiate_spec.rb +8 -0
- data/spec/lutaml/key_value/transformation_spec.rb +50 -50
- data/spec/lutaml/model/collection_spec.rb +43 -0
- data/spec/lutaml/model/schema/xml_compiler_runtime_spec.rb +81 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 411fee08073d8c3578b1071024486fa23ed6f1c3b93e918f7c47e175b9a95f90
|
|
4
|
+
data.tar.gz: 80e702fd74cda828374ffc7b5367311a2b49ce5ba2d5580e8038e13b6b9269ca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d48928bc46206f2067989a4637ace150fcf71db4df6c18e1df93de5c621c088db0490bf85d860ec6861ad08d208218553a5d2ae7806ea4b5e88067b0032d51ed
|
|
7
|
+
data.tar.gz: 8d0127e8139146f8d1df26a488904d7e48202e4c5b75a49f67a895f87d372507f9217253590496c44513677653bd8516f009d59f7889a41832c2e12333987585
|
|
@@ -697,21 +697,21 @@ end
|
|
|
697
697
|
|
|
698
698
|
=== Detection Logic
|
|
699
699
|
|
|
700
|
-
|
|
700
|
+
The transformation emits the output hash directly (hash-direct
|
|
701
|
+
emission), and every adapter serializes the hash it is given:
|
|
701
702
|
|
|
702
703
|
[source,ruby]
|
|
703
704
|
----
|
|
704
|
-
def to_json(
|
|
705
|
-
|
|
706
|
-
build_from_key_value_element(root)
|
|
707
|
-
elsif root.is_a?(Hash)
|
|
708
|
-
build_from_hash(root) # Legacy path
|
|
709
|
-
else
|
|
710
|
-
raise TypeError, "Expected KeyValueElement or Hash, got #{root.class}"
|
|
711
|
-
end
|
|
705
|
+
def to_json(hash, options = {})
|
|
706
|
+
JSON.generate(hash)
|
|
712
707
|
end
|
|
713
708
|
----
|
|
714
709
|
|
|
710
|
+
`Lutaml::KeyValue::DataModel::Element` remains the content model for
|
|
711
|
+
rule compilation and for custom `to:` handlers (whose scratch document
|
|
712
|
+
element merges its payload into the output hash), but the Element tree
|
|
713
|
+
is no longer passed across the adapter boundary.
|
|
714
|
+
|
|
715
715
|
=== Building from KeyValueElement
|
|
716
716
|
|
|
717
717
|
==== JSON Example
|
|
@@ -22,13 +22,7 @@ module Lutaml
|
|
|
22
22
|
def to_json(*args)
|
|
23
23
|
require "multi_json"
|
|
24
24
|
# Handle KeyValueElement input (new symmetric architecture)
|
|
25
|
-
attributes_to_serialize =
|
|
26
|
-
# Unwrap __root__ wrapper to get actual content
|
|
27
|
-
@attributes.to_hash["__root__"]
|
|
28
|
-
else
|
|
29
|
-
# Legacy Hash input (backward compatibility)
|
|
30
|
-
@attributes
|
|
31
|
-
end
|
|
25
|
+
attributes_to_serialize = @attributes
|
|
32
26
|
|
|
33
27
|
# LutaML's own threading keys must not reach the backend; the
|
|
34
28
|
# rest (e.g. :pretty) are meaningful to multi_json. The json
|
|
@@ -27,13 +27,7 @@ module Lutaml
|
|
|
27
27
|
def to_json(*args)
|
|
28
28
|
require "oj"
|
|
29
29
|
# Handle KeyValueElement input (new symmetric architecture)
|
|
30
|
-
attributes_to_serialize =
|
|
31
|
-
# Unwrap __root__ wrapper to get actual content
|
|
32
|
-
@attributes.to_hash["__root__"]
|
|
33
|
-
else
|
|
34
|
-
# Legacy Hash input (backward compatibility)
|
|
35
|
-
@attributes
|
|
36
|
-
end
|
|
30
|
+
attributes_to_serialize = @attributes
|
|
37
31
|
|
|
38
32
|
Oj.dump(attributes_to_serialize, *args)
|
|
39
33
|
# rubocop:enable Style/ArgumentsForwarding
|
|
@@ -22,13 +22,7 @@ module Lutaml
|
|
|
22
22
|
|
|
23
23
|
def to_json(*args)
|
|
24
24
|
# Handle KeyValueElement input (new symmetric architecture)
|
|
25
|
-
attributes_to_serialize =
|
|
26
|
-
# Unwrap __root__ wrapper to get actual content
|
|
27
|
-
@attributes.to_hash["__root__"]
|
|
28
|
-
else
|
|
29
|
-
# Legacy Hash input (backward compatibility)
|
|
30
|
-
@attributes
|
|
31
|
-
end
|
|
25
|
+
attributes_to_serialize = @attributes
|
|
32
26
|
|
|
33
27
|
unless GeneratorOptions.lutaml_options?(args.first)
|
|
34
28
|
return JSON.generate(attributes_to_serialize, args.first)
|
|
@@ -26,13 +26,7 @@ module Lutaml
|
|
|
26
26
|
def to_json(*args)
|
|
27
27
|
require "multi_json"
|
|
28
28
|
# Handle KeyValueElement input (new symmetric architecture)
|
|
29
|
-
attributes_to_serialize =
|
|
30
|
-
# Unwrap __root__ wrapper to get actual content
|
|
31
|
-
@attributes.to_hash["__root__"]
|
|
32
|
-
else
|
|
33
|
-
# Legacy Hash input (backward compatibility)
|
|
34
|
-
@attributes
|
|
35
|
-
end
|
|
29
|
+
attributes_to_serialize = @attributes
|
|
36
30
|
|
|
37
31
|
# LutaML's own options must not reach the backend: the json
|
|
38
32
|
# gem 3.x and Oj both raise on unknown generator options
|
|
@@ -31,13 +31,7 @@ module Lutaml
|
|
|
31
31
|
def to_json(*args)
|
|
32
32
|
require "oj"
|
|
33
33
|
# Handle KeyValueElement input (new symmetric architecture)
|
|
34
|
-
attributes_to_serialize =
|
|
35
|
-
# Unwrap __root__ wrapper to get actual content
|
|
36
|
-
@attributes.to_hash["__root__"]
|
|
37
|
-
else
|
|
38
|
-
# Legacy Hash input (backward compatibility)
|
|
39
|
-
@attributes
|
|
40
|
-
end
|
|
34
|
+
attributes_to_serialize = @attributes
|
|
41
35
|
|
|
42
36
|
Oj.dump(attributes_to_serialize, *args)
|
|
43
37
|
# rubocop:enable Style/ArgumentsForwarding
|
|
@@ -25,13 +25,7 @@ module Lutaml
|
|
|
25
25
|
|
|
26
26
|
def to_json(*args)
|
|
27
27
|
# Handle KeyValueElement input (new symmetric architecture)
|
|
28
|
-
attributes_to_serialize =
|
|
29
|
-
# Unwrap __root__ wrapper to get actual content
|
|
30
|
-
@attributes.to_hash["__root__"]
|
|
31
|
-
else
|
|
32
|
-
# Legacy Hash input (backward compatibility)
|
|
33
|
-
@attributes
|
|
34
|
-
end
|
|
28
|
+
attributes_to_serialize = @attributes
|
|
35
29
|
|
|
36
30
|
unless Lutaml::Json::GeneratorOptions.lutaml_options?(args.first)
|
|
37
31
|
return JSON.generate(attributes_to_serialize, args.first)
|
|
@@ -25,13 +25,7 @@ module Lutaml
|
|
|
25
25
|
def to_toml(*)
|
|
26
26
|
require "teptris"
|
|
27
27
|
# Handle KeyValueElement input (new symmetric architecture)
|
|
28
|
-
attributes_to_serialize =
|
|
29
|
-
# Unwrap __root__ wrapper to get actual content
|
|
30
|
-
@attributes.to_hash["__root__"]
|
|
31
|
-
else
|
|
32
|
-
# Legacy Hash input (backward compatibility)
|
|
33
|
-
@attributes
|
|
34
|
-
end
|
|
28
|
+
attributes_to_serialize = @attributes
|
|
35
29
|
|
|
36
30
|
Teptris::TOML.dump(attributes_to_serialize)
|
|
37
31
|
end
|
|
@@ -20,13 +20,7 @@ module Lutaml
|
|
|
20
20
|
def to_toml(*)
|
|
21
21
|
require "toml-rb"
|
|
22
22
|
# Handle KeyValueElement input (new symmetric architecture)
|
|
23
|
-
attributes_to_serialize =
|
|
24
|
-
# Unwrap __root__ wrapper to get actual content
|
|
25
|
-
@attributes.to_hash["__root__"]
|
|
26
|
-
else
|
|
27
|
-
# Legacy Hash input (backward compatibility)
|
|
28
|
-
@attributes
|
|
29
|
-
end
|
|
23
|
+
attributes_to_serialize = @attributes
|
|
30
24
|
|
|
31
25
|
TomlRB.dump(attributes_to_serialize)
|
|
32
26
|
rescue LoadError
|
|
@@ -25,13 +25,7 @@ module Lutaml
|
|
|
25
25
|
def to_toml(*)
|
|
26
26
|
require "tomlib"
|
|
27
27
|
# Handle KeyValueElement input (new symmetric architecture)
|
|
28
|
-
attributes_to_serialize =
|
|
29
|
-
# Unwrap __root__ wrapper to get actual content
|
|
30
|
-
@attributes.to_hash["__root__"]
|
|
31
|
-
else
|
|
32
|
-
# Legacy Hash input (backward compatibility)
|
|
33
|
-
@attributes
|
|
34
|
-
end
|
|
28
|
+
attributes_to_serialize = @attributes
|
|
35
29
|
|
|
36
30
|
Tomlib.dump(attributes_to_serialize)
|
|
37
31
|
# Tomlib::Generator.new(to_h).toml_str
|
|
@@ -24,13 +24,7 @@ module Lutaml
|
|
|
24
24
|
|
|
25
25
|
def to_yaml(options = {})
|
|
26
26
|
# Handle KeyValueElement input (new symmetric architecture)
|
|
27
|
-
attributes_to_serialize =
|
|
28
|
-
# Unwrap __root__ wrapper to get actual content
|
|
29
|
-
@attributes.to_hash["__root__"]
|
|
30
|
-
else
|
|
31
|
-
# Legacy Hash input (backward compatibility)
|
|
32
|
-
@attributes
|
|
33
|
-
end
|
|
27
|
+
attributes_to_serialize = @attributes
|
|
34
28
|
|
|
35
29
|
YAML.dump(attributes_to_serialize, options)
|
|
36
30
|
end
|
|
@@ -85,6 +85,21 @@ module Lutaml
|
|
|
85
85
|
has_value? && !has_children?
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
+
# The content this element carries, independent of its own key:
|
|
89
|
+
# the child-merged structure when children exist, otherwise the
|
|
90
|
+
# direct value (nil when neither). This is the half of to_hash
|
|
91
|
+
# that survives the {key => ...} envelope, so callers that
|
|
92
|
+
# assemble documents themselves never round-trip through a key.
|
|
93
|
+
#
|
|
94
|
+
# @return [Object, nil]
|
|
95
|
+
def payload
|
|
96
|
+
if has_children?
|
|
97
|
+
children_to_value
|
|
98
|
+
elsif has_value?
|
|
99
|
+
@value
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
88
103
|
# Convert to Hash representation
|
|
89
104
|
#
|
|
90
105
|
# This is the primary method for adapter rendering.
|
|
@@ -93,19 +108,10 @@ module Lutaml
|
|
|
93
108
|
# 2. If has children that are all Elements: {key => merged_hash}
|
|
94
109
|
# 3. If has children that are mixed/primitives: {key => array}
|
|
95
110
|
# 4. If has both value and children: children take precedence
|
|
96
|
-
# 5. Special case: __root__ with no children returns empty hash (for omitted attributes)
|
|
97
111
|
#
|
|
98
112
|
# @return [Hash]
|
|
99
113
|
def to_hash
|
|
100
|
-
|
|
101
|
-
{ @key => children_to_value }
|
|
102
|
-
elsif has_value?
|
|
103
|
-
{ @key => @value }
|
|
104
|
-
else
|
|
105
|
-
# Special case: __root__ element should return empty hash when all attributes omitted
|
|
106
|
-
# This allows hash["__root__"].keys to work without raising NoMethodError
|
|
107
|
-
{ @key => (@key == "__root__" ? {} : nil) }
|
|
108
|
-
end
|
|
114
|
+
{ @key => payload }
|
|
109
115
|
end
|
|
110
116
|
|
|
111
117
|
# String representation for debugging
|
|
@@ -123,43 +129,36 @@ module Lutaml
|
|
|
123
129
|
|
|
124
130
|
# Hash-like access for backward compatibility
|
|
125
131
|
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
132
|
+
# A child element's payload answers by the child's key; a leaf
|
|
133
|
+
# answers by its own key ({"name" => "John"}["name"]). This is
|
|
134
|
+
# what lets custom `to:` handlers build nested structures in
|
|
135
|
+
# place: `doc["locality"] = {}; doc["locality"]["type"] = v` —
|
|
136
|
+
# the read returns the child's value hash and the write mutates
|
|
137
|
+
# it in place.
|
|
128
138
|
#
|
|
129
139
|
# @param key [String, Symbol] Key to access
|
|
130
140
|
# @return [Object] Value for the key
|
|
131
141
|
def [](key)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
inner = hash["__root__"]
|
|
136
|
-
inner.is_a?(Hash) ? inner[key.to_s] : nil
|
|
137
|
-
else
|
|
138
|
-
hash[key.to_s]
|
|
139
|
-
end
|
|
142
|
+
key = key.to_s
|
|
143
|
+
child = @children.find { |c| c.is_a?(Element) && c.key == key }
|
|
144
|
+
child ? child.payload : to_hash[key]
|
|
140
145
|
end
|
|
141
146
|
|
|
142
147
|
# Hash-like assignment for backward compatibility
|
|
143
148
|
#
|
|
144
149
|
# Allows Element to be used like a hash in tests and existing code.
|
|
145
|
-
#
|
|
150
|
+
# Replaces any existing child element of the same key, then adds
|
|
151
|
+
# a new child Element for the key-value pair.
|
|
146
152
|
#
|
|
147
153
|
# @param key [String, Symbol] Key to set
|
|
148
154
|
# @param value [Object] Value to set
|
|
149
155
|
# @return [Object] The assigned value
|
|
150
156
|
def []=(key, value)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
@children.reject! do |child|
|
|
155
|
-
child.is_a?(Element) && child.key == key.to_s
|
|
156
|
-
end
|
|
157
|
-
# Add new child
|
|
158
|
-
else
|
|
159
|
-
# For non-root elements, convert to hash and merge
|
|
160
|
-
# This shouldn't normally happen in the transformation flow
|
|
157
|
+
key = key.to_s
|
|
158
|
+
@children.reject! do |child|
|
|
159
|
+
child.is_a?(Element) && child.key == key
|
|
161
160
|
end
|
|
162
|
-
@children << Element.new(key
|
|
161
|
+
@children << Element.new(key, value)
|
|
163
162
|
value
|
|
164
163
|
end
|
|
165
164
|
|
|
@@ -57,9 +57,8 @@ module Lutaml
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def hash_mappings
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@child_mappings
|
|
60
|
+
@hash_mappings = @root_mappings || @child_mappings if @hash_mappings.nil?
|
|
61
|
+
@hash_mappings
|
|
63
62
|
end
|
|
64
63
|
|
|
65
64
|
def deep_dup
|
|
@@ -81,7 +80,8 @@ module Lutaml
|
|
|
81
80
|
end
|
|
82
81
|
|
|
83
82
|
def root_mapping?
|
|
84
|
-
name == "root_mapping"
|
|
83
|
+
@root_mapping = name == "root_mapping" if @root_mapping.nil?
|
|
84
|
+
@root_mapping
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
# The writer symbol for this rule's target, interned once per
|
|
@@ -28,7 +28,9 @@ module Lutaml
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
if model_class.include?(Lutaml::Model::Serialize)
|
|
31
|
-
|
|
31
|
+
# #1003: constructor-equivalent state via the compiled walk
|
|
32
|
+
# seed — no per-attribute determine_value walk per instance.
|
|
33
|
+
instance = model_class.allocate_for_walk(child_register)
|
|
32
34
|
else
|
|
33
35
|
instance = model_class.new
|
|
34
36
|
register_accessor_methods_for(instance, child_register)
|
|
@@ -104,8 +106,7 @@ module Lutaml
|
|
|
104
106
|
# transformation_for returns nil for cyclic dependencies or :building sentinel
|
|
105
107
|
# Fall back to legacy approach in these cases
|
|
106
108
|
if transformation.is_a?(Lutaml::KeyValue::Transformation)
|
|
107
|
-
# transform emits the output hash directly (TODO.perf 19)
|
|
108
|
-
# the __root__ Element tree and its to_hash flatten are gone.
|
|
109
|
+
# transform emits the output hash directly (TODO.perf 19)
|
|
109
110
|
return transformation.transform(instance, options)
|
|
110
111
|
end
|
|
111
112
|
end
|
|
@@ -430,7 +431,7 @@ child_hash, format, { mappings: type.child_rules })
|
|
|
430
431
|
children << child
|
|
431
432
|
end
|
|
432
433
|
end
|
|
433
|
-
instance = model_class.
|
|
434
|
+
instance = model_class.allocate_for_walk(register)
|
|
434
435
|
setters.each { |name, value| instance.public_send(name, value) }
|
|
435
436
|
absent&.each do |rule|
|
|
436
437
|
process_mapping_rule(doc, instance, format, rule, options, nil)
|
|
@@ -477,7 +478,7 @@ options)
|
|
|
477
478
|
to_method = rule.custom_methods[:to]
|
|
478
479
|
# lutaml-model#550: custom methods may declare a third context
|
|
479
480
|
# parameter to receive the options passed to `to_*`.
|
|
480
|
-
if instance.
|
|
481
|
+
if Lutaml::Model::MappingRule.custom_method_arity(instance.class, to_method).to_i >= 3
|
|
481
482
|
return instance.public_send(to_method, instance, hash,
|
|
482
483
|
options[:context])
|
|
483
484
|
end
|
|
@@ -221,9 +221,8 @@ register = self.register)
|
|
|
221
221
|
def transform(model_instance, options = {})
|
|
222
222
|
# For key-value formats, we typically don't have a named root
|
|
223
223
|
# Instead, we create an anonymous root that holds all attributes
|
|
224
|
-
# Hash-direct emission (TODO.perf 19): the
|
|
225
|
-
#
|
|
226
|
-
# it straight back to a hash. The output hash is built directly.
|
|
224
|
+
# Hash-direct emission (TODO.perf 19): the output hash is built
|
|
225
|
+
# directly; the intermediate Element tree and its flatten are gone.
|
|
227
226
|
root = {}
|
|
228
227
|
|
|
229
228
|
# Apply each compiled rule (with filtering support). A
|
|
@@ -343,9 +342,10 @@ register = self.register)
|
|
|
343
342
|
if rule.has_custom_methods? && rule.custom_methods[:to]
|
|
344
343
|
to_method = rule.custom_methods[:to]
|
|
345
344
|
# Custom methods receive an Element "doc" and populate it
|
|
346
|
-
# (public contract, TODO.perf 19 keeps it):
|
|
347
|
-
#
|
|
348
|
-
|
|
345
|
+
# (public contract, TODO.perf 19 keeps it): only the doc's
|
|
346
|
+
# payload — the merged children or the direct value hash —
|
|
347
|
+
# is merged into the output.
|
|
348
|
+
doc = Lutaml::KeyValue::DataModel::Element.new(rule.serialized_name)
|
|
349
349
|
# Custom methods may write children, or doc.value[key]=, or
|
|
350
350
|
# nothing at all (pubid 2.0 regression: an empty scratch
|
|
351
351
|
# flattened to {key => nil} and crashed the merge). Seed the
|
|
@@ -353,7 +353,7 @@ register = self.register)
|
|
|
353
353
|
doc.value = {}
|
|
354
354
|
# lutaml-model#550: custom methods may declare a third context
|
|
355
355
|
# parameter to receive the options passed to `to_*`.
|
|
356
|
-
if model_instance.
|
|
356
|
+
if Lutaml::Model::MappingRule.custom_method_arity(model_instance.class, to_method).to_i >= 3
|
|
357
357
|
model_instance.public_send(to_method, model_instance,
|
|
358
358
|
doc, options[:context])
|
|
359
359
|
else
|
|
@@ -363,11 +363,11 @@ register = self.register)
|
|
|
363
363
|
# its return value is ignored. (A mutation-style handler whose
|
|
364
364
|
# last statement is an assignment returns the assigned inner
|
|
365
365
|
# value; honoring it merged child content at the wrong level —
|
|
366
|
-
# lutaml-model#892.)
|
|
367
|
-
#
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
366
|
+
# lutaml-model#892.) A doc whose payload is not a hash — nil
|
|
367
|
+
# (untouched), a scalar, or an array of primitives — serializes
|
|
368
|
+
# as nothing, matching the pre-0.8.74 behavior.
|
|
369
|
+
merged = doc.payload
|
|
370
|
+
parent.update(merged) if merged.is_a?(::Hash)
|
|
371
371
|
return
|
|
372
372
|
end
|
|
373
373
|
|
|
@@ -539,8 +539,7 @@ converted_from_empty_to_nil: false, converted_from_nil_to_empty: false)
|
|
|
539
539
|
value_root = value_transformation.transform(attr_value,
|
|
540
540
|
options)
|
|
541
541
|
|
|
542
|
-
# transform emits the output hash directly
|
|
543
|
-
# (TODO.perf 19) — no __root__ wrapper to unwrap.
|
|
542
|
+
# transform emits the output hash directly (TODO.perf 19)
|
|
544
543
|
parent[key_value.to_s] = value_root
|
|
545
544
|
next
|
|
546
545
|
end
|
|
@@ -1147,10 +1146,14 @@ child_mappings, options)
|
|
|
1147
1146
|
parsed_content.each do |key, val|
|
|
1148
1147
|
parent[key.to_s] = val
|
|
1149
1148
|
end
|
|
1150
|
-
elsif parsed_content.
|
|
1151
|
-
#
|
|
1152
|
-
# the
|
|
1153
|
-
|
|
1149
|
+
elsif !parsed_content.nil?
|
|
1150
|
+
# A raw mapping's content must be a key-value document — it is
|
|
1151
|
+
# merged into the parent by key. Any other shape (an Array, a
|
|
1152
|
+
# scalar) has no representation here; serializing it under a
|
|
1153
|
+
# synthetic key would leak that key into the document.
|
|
1154
|
+
raise ArgumentError,
|
|
1155
|
+
"raw mapping content must parse to a Hash, got " \
|
|
1156
|
+
"#{parsed_content.class}"
|
|
1154
1157
|
end
|
|
1155
1158
|
end
|
|
1156
1159
|
|
|
@@ -394,6 +394,18 @@ module Lutaml
|
|
|
394
394
|
@options[:initialize_empty]
|
|
395
395
|
end
|
|
396
396
|
|
|
397
|
+
# True when the default is the empty-collection lambda installed by
|
|
398
|
+
# set_default_for_collection: the one default whose constructor
|
|
399
|
+
# state (a plain empty Array) the LAZY_EMPTY_COLLECTION sentinel
|
|
400
|
+
# materializes to on first read, so walk-state seeding can use the
|
|
401
|
+
# sentinel instead of running the lambda per instance (#1010).
|
|
402
|
+
# Restricted to plain-Array collections because the lazy reader
|
|
403
|
+
# materializes []; custom Collection classes would lose their type.
|
|
404
|
+
def lazy_collection_default?
|
|
405
|
+
@implicit_collection_default == true && collection? &&
|
|
406
|
+
collection_class == ::Array && !enum?
|
|
407
|
+
end
|
|
408
|
+
|
|
397
409
|
def validations
|
|
398
410
|
@options[:validations]
|
|
399
411
|
end
|
|
@@ -624,6 +636,13 @@ instance_object = nil)
|
|
|
624
636
|
value.frozen?
|
|
625
637
|
end
|
|
626
638
|
|
|
639
|
+
# Static classification of this attribute's default form (:none,
|
|
640
|
+
# :static, :proc, :delegate) — the constructor's determine_value
|
|
641
|
+
# and the compiled walk-seed share it.
|
|
642
|
+
def default_kind
|
|
643
|
+
@default_kind ||= _default_kind
|
|
644
|
+
end
|
|
645
|
+
|
|
627
646
|
def default_value(register, instance_object = nil)
|
|
628
647
|
# The kind is fixed at definition; the common case (no default
|
|
629
648
|
# at all) paid three option checks per absent attribute per
|
|
@@ -1368,7 +1387,10 @@ instance_object = nil)
|
|
|
1368
1387
|
def set_default_for_collection
|
|
1369
1388
|
validate_collection_range
|
|
1370
1389
|
attr = self
|
|
1371
|
-
|
|
1390
|
+
if initialize_empty? && !@options[:default]
|
|
1391
|
+
@options[:default] = -> { attr.build_collection }
|
|
1392
|
+
@implicit_collection_default = true
|
|
1393
|
+
end
|
|
1372
1394
|
end
|
|
1373
1395
|
|
|
1374
1396
|
def validate_options!(options)
|
|
@@ -384,11 +384,7 @@ module Lutaml
|
|
|
384
384
|
private
|
|
385
385
|
|
|
386
386
|
def unwrap_unwrapped_data(data)
|
|
387
|
-
|
|
388
|
-
hash = data.is_a?(Hash) ? data : data.to_hash
|
|
389
|
-
# Handle "__root__" wrapper for key-value formats (created by transformation)
|
|
390
|
-
hash = hash["__root__"] if hash.key?("__root__")
|
|
391
|
-
result = Utils.fetch_str_or_sym(hash, instance_name)
|
|
387
|
+
result = Utils.fetch_str_or_sym(data, instance_name)
|
|
392
388
|
|
|
393
389
|
# Extract values from nested hashes with empty string keys
|
|
394
390
|
# (created by transformation for simple models with single attribute)
|
|
@@ -300,12 +300,31 @@ module Lutaml
|
|
|
300
300
|
end
|
|
301
301
|
end
|
|
302
302
|
|
|
303
|
+
# Arity of a custom (from:/to:) method, memoized per class+method:
|
|
304
|
+
# the reflection (method()/parameters) allocated per call and ran
|
|
305
|
+
# per value in the serialize/deserialize hot loops (42% of a
|
|
306
|
+
# custom-heavy to_json profile).
|
|
307
|
+
def self.custom_method_arity(klass, method_name)
|
|
308
|
+
per_klass = (@custom_method_arities ||= {}.compare_by_identity)
|
|
309
|
+
cache = (per_klass[klass] ||= {}.compare_by_identity)
|
|
310
|
+
cached = cache[method_name]
|
|
311
|
+
return cached if cached
|
|
312
|
+
|
|
313
|
+
begin
|
|
314
|
+
arity = klass.instance_method(method_name).parameters.size
|
|
315
|
+
rescue NameError
|
|
316
|
+
arity = nil
|
|
317
|
+
end
|
|
318
|
+
cache[method_name] = arity
|
|
319
|
+
arity
|
|
320
|
+
end
|
|
321
|
+
|
|
303
322
|
def serialize(model, parent = nil, doc = nil, context = nil)
|
|
304
323
|
if custom_methods[:to]
|
|
305
324
|
to_method = custom_methods[:to]
|
|
306
325
|
# lutaml-model#550: custom methods may declare a fourth
|
|
307
326
|
# context parameter to receive the options passed to `to_*`.
|
|
308
|
-
if model.
|
|
327
|
+
if self.class.custom_method_arity(model.class, to_method).to_i >= 4
|
|
309
328
|
model.public_send(to_method, model, parent, doc, context)
|
|
310
329
|
else
|
|
311
330
|
model.public_send(to_method, model, parent, doc)
|
|
@@ -555,7 +574,7 @@ context = nil, pre_cast: false)
|
|
|
555
574
|
target = mapper_class.new
|
|
556
575
|
# lutaml-model#550: custom methods may declare a third context
|
|
557
576
|
# parameter to receive the options passed to `from_*`.
|
|
558
|
-
if target.
|
|
577
|
+
if self.class.custom_method_arity(target.class, custom).to_i >= 3
|
|
559
578
|
target.public_send(custom, model, value, context)
|
|
560
579
|
else
|
|
561
580
|
target.public_send(custom, model, value)
|
|
@@ -21,12 +21,17 @@ module Lutaml
|
|
|
21
21
|
@parent = parent
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
# <xs:complexType> -> Definitions::Model.
|
|
25
|
-
|
|
24
|
+
# <xs:complexType> -> Definitions::Model. Anonymous types
|
|
25
|
+
# carry the owning element's name as `root_name` so the
|
|
26
|
+
# generated class serializes the element, not the internal
|
|
27
|
+
# CT_ alias.
|
|
28
|
+
def build(complex_type, root_name: nil)
|
|
26
29
|
model = Definitions::Model.new(
|
|
27
30
|
class_name: Utils.camel_case(complex_type.name),
|
|
28
|
-
xml_root: Definitions::XmlRoot.new(
|
|
29
|
-
|
|
31
|
+
xml_root: Definitions::XmlRoot.new(
|
|
32
|
+
kind: :element,
|
|
33
|
+
name: root_name || complex_type.name,
|
|
34
|
+
),
|
|
30
35
|
mixed: !!complex_type.mixed,
|
|
31
36
|
namespace_class_name: @parent.namespace_class_name,
|
|
32
37
|
)
|
|
@@ -110,8 +110,11 @@ module Lutaml
|
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
local = Utils.last_of_split(raw_type)
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
primitive = SupportedDataTypes.primitive_symbol(local)
|
|
114
|
+
Definitions::TypeRef.new(
|
|
115
|
+
kind: :symbol,
|
|
116
|
+
value: primitive || Utils.snake_case(local),
|
|
117
|
+
)
|
|
115
118
|
end
|
|
116
119
|
|
|
117
120
|
private
|
|
@@ -169,8 +172,10 @@ module Lutaml
|
|
|
169
172
|
element.simple_type)
|
|
170
173
|
end
|
|
171
174
|
if element.complex_type
|
|
172
|
-
return register_anonymous_complex_type(
|
|
173
|
-
|
|
175
|
+
return register_anonymous_complex_type(
|
|
176
|
+
"CT_#{element.name}", element.complex_type,
|
|
177
|
+
root_name: element.name
|
|
178
|
+
)
|
|
174
179
|
end
|
|
175
180
|
|
|
176
181
|
"string"
|
|
@@ -184,8 +189,10 @@ module Lutaml
|
|
|
184
189
|
end
|
|
185
190
|
|
|
186
191
|
if item.respond_to?(:complex_type) && item.complex_type
|
|
187
|
-
register_anonymous_complex_type(
|
|
188
|
-
|
|
192
|
+
register_anonymous_complex_type(
|
|
193
|
+
"CT_#{item.name}", item.complex_type,
|
|
194
|
+
root_name: item.name
|
|
195
|
+
)
|
|
189
196
|
end
|
|
190
197
|
end
|
|
191
198
|
|
|
@@ -196,10 +203,10 @@ module Lutaml
|
|
|
196
203
|
anon_name
|
|
197
204
|
end
|
|
198
205
|
|
|
199
|
-
def register_anonymous_complex_type(anon_name, anon_node)
|
|
206
|
+
def register_anonymous_complex_type(anon_name, anon_node, root_name: nil)
|
|
200
207
|
anon_node.name = anon_name
|
|
201
208
|
@parent.complex_types[anon_name] =
|
|
202
|
-
@parent.build_complex_type(anon_node)
|
|
209
|
+
@parent.build_complex_type(anon_node, root_name: root_name)
|
|
203
210
|
anon_name
|
|
204
211
|
end
|
|
205
212
|
|
|
@@ -156,8 +156,8 @@ module Lutaml
|
|
|
156
156
|
@simple_types_builder.build(simple_type)
|
|
157
157
|
end
|
|
158
158
|
|
|
159
|
-
def build_complex_type(complex_type)
|
|
160
|
-
@complex_types_builder.build(complex_type)
|
|
159
|
+
def build_complex_type(complex_type, root_name: nil)
|
|
160
|
+
@complex_types_builder.build(complex_type, root_name: root_name)
|
|
161
161
|
end
|
|
162
162
|
|
|
163
163
|
private
|