lutaml-model 0.8.130 → 0.8.132
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/transform.rb +24 -8
- data/lib/lutaml/key_value/transformation/value_serializer.rb +14 -2
- data/lib/lutaml/key_value/transformation.rb +33 -20
- data/lib/lutaml/model/attribute.rb +15 -2
- data/lib/lutaml/model/collection.rb +1 -5
- 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 +57 -4
- data/lib/lutaml/model/transform.rb +78 -25
- data/lib/lutaml/model/type_context.rb +9 -0
- 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/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: 83ece4646bc4f2ecd052f6ba5edf994cb512b4be3a08e8a1778c5b357daab424
|
|
4
|
+
data.tar.gz: 9bac20d16559163de764a7148974724c199cacbce0d88d945b932c7d36ccaa19
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 27120683de1ff8d9c95049e683dffdf302aa43edf9a4e5e6debda189df3903fdf98f4722200eb4106285e86dfabb170b77fcaea3313c6e1a8e8e548ec2db0eab
|
|
7
|
+
data.tar.gz: 26a84cc1b288f7b829c1bdb44360adc7211843e506d1d51c1f416c411636fa6099dee79fd26d4d41a7e22b5eaf3ef1510d9580dbe49318ac8c2b9e02b80974bc
|
|
@@ -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
|
|
|
@@ -106,8 +106,7 @@ module Lutaml
|
|
|
106
106
|
# transformation_for returns nil for cyclic dependencies or :building sentinel
|
|
107
107
|
# Fall back to legacy approach in these cases
|
|
108
108
|
if transformation.is_a?(Lutaml::KeyValue::Transformation)
|
|
109
|
-
# transform emits the output hash directly (TODO.perf 19)
|
|
110
|
-
# the __root__ Element tree and its to_hash flatten are gone.
|
|
109
|
+
# transform emits the output hash directly (TODO.perf 19)
|
|
111
110
|
return transformation.transform(instance, options)
|
|
112
111
|
end
|
|
113
112
|
end
|
|
@@ -347,15 +346,21 @@ child_mappings)
|
|
|
347
346
|
# path. Returns nil (caller falls back) on any shape the plan
|
|
348
347
|
# does not cover, e.g. a non-Hash item where a model was expected.
|
|
349
348
|
def kv_group_build(rows, doc, format, register, options)
|
|
350
|
-
|
|
349
|
+
# Positional values, not per-row [name, value] pairs: rows are a
|
|
350
|
+
# fixed per-class list, so the writer to apply lives at the row's
|
|
351
|
+
# index and the pair arrays were the lane's biggest allocation
|
|
352
|
+
# (~6 arrays per node on hydrate-heavy corpora).
|
|
353
|
+
values = Array.new(rows.size, MISSING)
|
|
351
354
|
children = []
|
|
352
355
|
# Lazy: the present-key hot path never pushes, and this runs
|
|
353
356
|
# once per hydrated model instance.
|
|
354
357
|
absent = nil
|
|
355
358
|
unmapped = nil
|
|
359
|
+
i = 0
|
|
356
360
|
rows.each do |rule, attr, kind, type, child_rows, child_register|
|
|
357
361
|
if kind == :unmapped
|
|
358
362
|
unmapped = [type, child_rows]
|
|
363
|
+
i += 1
|
|
359
364
|
next
|
|
360
365
|
end
|
|
361
366
|
|
|
@@ -365,6 +370,7 @@ child_mappings)
|
|
|
365
370
|
v = Lutaml::Model::Utils.fetch_str_or_sym(doc, rule.name, MISSING)
|
|
366
371
|
if v.equal?(MISSING)
|
|
367
372
|
(absent ||= []) << rule
|
|
373
|
+
i += 1
|
|
368
374
|
next
|
|
369
375
|
end
|
|
370
376
|
|
|
@@ -390,13 +396,15 @@ child_mappings)
|
|
|
390
396
|
built << self.class.data_to_model(type.attr_type,
|
|
391
397
|
child_hash, format, { mappings: type.child_rules })
|
|
392
398
|
end
|
|
393
|
-
|
|
399
|
+
values[i] = built
|
|
394
400
|
children.concat(built)
|
|
401
|
+
i += 1
|
|
395
402
|
next
|
|
396
403
|
end
|
|
397
404
|
|
|
398
405
|
if kind == :scalar
|
|
399
|
-
|
|
406
|
+
values[i] = v
|
|
407
|
+
i += 1
|
|
400
408
|
next
|
|
401
409
|
end
|
|
402
410
|
|
|
@@ -418,7 +426,7 @@ child_hash, format, { mappings: type.child_rules })
|
|
|
418
426
|
|
|
419
427
|
built << child
|
|
420
428
|
end
|
|
421
|
-
|
|
429
|
+
values[i] = built
|
|
422
430
|
children.concat(built)
|
|
423
431
|
else
|
|
424
432
|
return nil unless v.is_a?(::Hash)
|
|
@@ -428,12 +436,20 @@ child_hash, format, { mappings: type.child_rules })
|
|
|
428
436
|
options)
|
|
429
437
|
return nil if child.nil?
|
|
430
438
|
|
|
431
|
-
|
|
439
|
+
values[i] = child
|
|
432
440
|
children << child
|
|
433
441
|
end
|
|
442
|
+
i += 1
|
|
434
443
|
end
|
|
435
444
|
instance = model_class.allocate_for_walk(register)
|
|
436
|
-
|
|
445
|
+
i = 0
|
|
446
|
+
rows.each do |rule, _attr, kind, *_rest|
|
|
447
|
+
v = values[i]
|
|
448
|
+
i += 1
|
|
449
|
+
next if v.equal?(MISSING) || kind == :unmapped
|
|
450
|
+
|
|
451
|
+
instance.public_send(rule.kv_setter, v)
|
|
452
|
+
end
|
|
437
453
|
absent&.each do |rule|
|
|
438
454
|
process_mapping_rule(doc, instance, format, rule, options, nil)
|
|
439
455
|
end
|
|
@@ -43,6 +43,9 @@ model_class: nil)
|
|
|
43
43
|
@register_id = register_id
|
|
44
44
|
@transformation_factory = transformation_factory
|
|
45
45
|
@model_class = model_class
|
|
46
|
+
# Pre-declared: the serializer may be frozen after setup, and
|
|
47
|
+
# the type-transformation memo mutates this hash in place.
|
|
48
|
+
@lutaml_type_transformations = {}
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
# Serialize a value for an item (handles nested models and primitives).
|
|
@@ -117,10 +120,10 @@ model_class: nil)
|
|
|
117
120
|
|
|
118
121
|
# Get or create child transformation
|
|
119
122
|
child_transformation = if uses_polymorphism
|
|
120
|
-
|
|
123
|
+
transformation_for_type(actual_type)
|
|
121
124
|
else
|
|
122
125
|
rule.child_transformation ||
|
|
123
|
-
|
|
126
|
+
transformation_for_type(rule.attribute_type)
|
|
124
127
|
end
|
|
125
128
|
|
|
126
129
|
if child_transformation
|
|
@@ -296,6 +299,15 @@ model_class: nil)
|
|
|
296
299
|
transformation_factory.call(type_class)
|
|
297
300
|
end
|
|
298
301
|
|
|
302
|
+
# Memoized on this object: the factory binds this object's
|
|
303
|
+
# mapping/format/register, so the memo is register-safe, and
|
|
304
|
+
# building one per serialized node is a Transformation per
|
|
305
|
+
# nested model per instance.
|
|
306
|
+
def transformation_for_type(type_class)
|
|
307
|
+
(@lutaml_type_transformations ||= {})[type_class] ||=
|
|
308
|
+
create_transformation(type_class)
|
|
309
|
+
end
|
|
310
|
+
|
|
299
311
|
# Transform a nested model using the child transformation.
|
|
300
312
|
#
|
|
301
313
|
# @param value [Object] The model instance
|
|
@@ -63,6 +63,9 @@ module Lutaml
|
|
|
63
63
|
|
|
64
64
|
# Set up serializers before calling super
|
|
65
65
|
# The super will call compile_rules and freeze
|
|
66
|
+
# Pre-declared: the transformation is frozen after compile, and
|
|
67
|
+
# the type-transformation memo mutates this hash in place.
|
|
68
|
+
@lutaml_type_transformations = {}
|
|
66
69
|
@rule_compiler = RuleCompiler.new(
|
|
67
70
|
model_class: model_class,
|
|
68
71
|
register_id: register_id,
|
|
@@ -221,9 +224,8 @@ register = self.register)
|
|
|
221
224
|
def transform(model_instance, options = {})
|
|
222
225
|
# For key-value formats, we typically don't have a named root
|
|
223
226
|
# 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.
|
|
227
|
+
# Hash-direct emission (TODO.perf 19): the output hash is built
|
|
228
|
+
# directly; the intermediate Element tree and its flatten are gone.
|
|
227
229
|
root = {}
|
|
228
230
|
|
|
229
231
|
# Apply each compiled rule (with filtering support). A
|
|
@@ -343,9 +345,10 @@ register = self.register)
|
|
|
343
345
|
if rule.has_custom_methods? && rule.custom_methods[:to]
|
|
344
346
|
to_method = rule.custom_methods[:to]
|
|
345
347
|
# Custom methods receive an Element "doc" and populate it
|
|
346
|
-
# (public contract, TODO.perf 19 keeps it):
|
|
347
|
-
#
|
|
348
|
-
|
|
348
|
+
# (public contract, TODO.perf 19 keeps it): only the doc's
|
|
349
|
+
# payload — the merged children or the direct value hash —
|
|
350
|
+
# is merged into the output.
|
|
351
|
+
doc = Lutaml::KeyValue::DataModel::Element.new(rule.serialized_name)
|
|
349
352
|
# Custom methods may write children, or doc.value[key]=, or
|
|
350
353
|
# nothing at all (pubid 2.0 regression: an empty scratch
|
|
351
354
|
# flattened to {key => nil} and crashed the merge). Seed the
|
|
@@ -363,11 +366,11 @@ register = self.register)
|
|
|
363
366
|
# its return value is ignored. (A mutation-style handler whose
|
|
364
367
|
# last statement is an assignment returns the assigned inner
|
|
365
368
|
# value; honoring it merged child content at the wrong level —
|
|
366
|
-
# lutaml-model#892.)
|
|
367
|
-
#
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
369
|
+
# lutaml-model#892.) A doc whose payload is not a hash — nil
|
|
370
|
+
# (untouched), a scalar, or an array of primitives — serializes
|
|
371
|
+
# as nothing, matching the pre-0.8.74 behavior.
|
|
372
|
+
merged = doc.payload
|
|
373
|
+
parent.update(merged) if merged.is_a?(::Hash)
|
|
371
374
|
return
|
|
372
375
|
end
|
|
373
376
|
|
|
@@ -539,8 +542,7 @@ converted_from_empty_to_nil: false, converted_from_nil_to_empty: false)
|
|
|
539
542
|
value_root = value_transformation.transform(attr_value,
|
|
540
543
|
options)
|
|
541
544
|
|
|
542
|
-
# transform emits the output hash directly
|
|
543
|
-
# (TODO.perf 19) — no __root__ wrapper to unwrap.
|
|
545
|
+
# transform emits the output hash directly (TODO.perf 19)
|
|
544
546
|
parent[key_value.to_s] = value_root
|
|
545
547
|
next
|
|
546
548
|
end
|
|
@@ -1035,6 +1037,15 @@ child_mappings, options)
|
|
|
1035
1037
|
# @param value [Object] The value
|
|
1036
1038
|
# @param options [Hash] Options
|
|
1037
1039
|
# @return [Object] The value (could be Hash, primitive, or KeyValueElement)
|
|
1040
|
+
# Child transformations memoized on this transformation instance:
|
|
1041
|
+
# the factory binds this instance's mapping/format/register, so
|
|
1042
|
+
# the memo is register-safe, and building one per serialized node
|
|
1043
|
+
# is a Transformation per nested model per instance.
|
|
1044
|
+
def transformation_for_type(type_class)
|
|
1045
|
+
(@lutaml_type_transformations ||= {})[type_class] ||=
|
|
1046
|
+
build_child_transformation(type_class)
|
|
1047
|
+
end
|
|
1048
|
+
|
|
1038
1049
|
def create_value_for_item(rule, value, options)
|
|
1039
1050
|
return nil if value.nil?
|
|
1040
1051
|
|
|
@@ -1099,9 +1110,7 @@ child_mappings, options)
|
|
|
1099
1110
|
if !child_transformation || uses_polymorphism
|
|
1100
1111
|
# For polymorphic types, use the actual runtime type's mappings
|
|
1101
1112
|
type_for_mapping = uses_polymorphism ? actual_type : rule.attribute_type
|
|
1102
|
-
|
|
1103
|
-
child_transformation = self.class.new(type_for_mapping, mapping,
|
|
1104
|
-
format, register)
|
|
1113
|
+
child_transformation = transformation_for_type(type_for_mapping)
|
|
1105
1114
|
end
|
|
1106
1115
|
|
|
1107
1116
|
if child_transformation
|
|
@@ -1147,10 +1156,14 @@ child_mappings, options)
|
|
|
1147
1156
|
parsed_content.each do |key, val|
|
|
1148
1157
|
parent[key.to_s] = val
|
|
1149
1158
|
end
|
|
1150
|
-
elsif parsed_content.
|
|
1151
|
-
#
|
|
1152
|
-
# the
|
|
1153
|
-
|
|
1159
|
+
elsif !parsed_content.nil?
|
|
1160
|
+
# A raw mapping's content must be a key-value document — it is
|
|
1161
|
+
# merged into the parent by key. Any other shape (an Array, a
|
|
1162
|
+
# scalar) has no representation here; serializing it under a
|
|
1163
|
+
# synthetic key would leak that key into the document.
|
|
1164
|
+
raise ArgumentError,
|
|
1165
|
+
"raw mapping content must parse to a Hash, got " \
|
|
1166
|
+
"#{parsed_content.class}"
|
|
1154
1167
|
end
|
|
1155
1168
|
end
|
|
1156
1169
|
|
|
@@ -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
|
|
@@ -1375,8 +1387,9 @@ instance_object = nil)
|
|
|
1375
1387
|
def set_default_for_collection
|
|
1376
1388
|
validate_collection_range
|
|
1377
1389
|
attr = self
|
|
1378
|
-
if initialize_empty?
|
|
1379
|
-
@options[:default]
|
|
1390
|
+
if initialize_empty? && !@options[:default]
|
|
1391
|
+
@options[:default] = -> { attr.build_collection }
|
|
1392
|
+
@implicit_collection_default = true
|
|
1380
1393
|
end
|
|
1381
1394
|
end
|
|
1382
1395
|
|
|
@@ -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)
|
|
@@ -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
|
)
|