lutaml-model 0.8.73 → 0.8.74

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: b25c7b3e06beee19bd00eeec495484f446a206aed30db2b247a6b493ddb417ae
4
- data.tar.gz: 9a4870046688f1dd8076fcbd8bfd10e2a3cbb24a15608413be2b006d0209b7dc
3
+ metadata.gz: 45577608605b19115a3826164cf851363218b65e71efa0b48a0d25e3a0df913f
4
+ data.tar.gz: '0181c1f69c5d731ad13dd65fa31a37c929714f3a0a7fd12d9dd59f7e0d093b1a'
5
5
  SHA512:
6
- metadata.gz: 214c201878c903a3205368c89547f4ad22fb8b2e925b19420dda4e90db28dca9d26d66aa89d50b232ded8dc5258e92f95ff9088cebe9f93a0acf04df626a99a4
7
- data.tar.gz: '06390a0d5f7df047f947931c9d1d4f96b970e2a0a890f1e356c10a8e3fbc10bcdc5bbf4d4c01ca5221757f0eac0b9ffae91aabf264368211e5ce18b490a45e65'
6
+ metadata.gz: ea5765db2e067d6d9b5670eaaa3c4f1269bdea9486ed07179ffa048e4435c0a504542dbf9dbdc0f270f776d084511d7d91998425b024fd8516f3599b7d238616
7
+ data.tar.gz: 88db0489938297bd0d74e24d169fbf6aecc0245998401159e0795122e24580e050e9dff58672c481f8b7fa14c64314305b23c3d7780962023282fa66a1d549c3
@@ -56,13 +56,9 @@ module Lutaml
56
56
  # transformation_for returns nil for cyclic dependencies or :building sentinel
57
57
  # Fall back to legacy approach in these cases
58
58
  if transformation.is_a?(Lutaml::KeyValue::Transformation)
59
- # Use new Transformation to get KeyValueElement
60
- kv_element = transformation.transform(instance, options)
61
- # Convert KeyValueElement to hash for backward compatibility with adapters
62
- # The to_hash method returns {"__root__" => {actual_hash}}
63
- kv_hash = kv_element.to_hash
64
- # For root element, return just the content hash
65
- return kv_hash["__root__"] || kv_hash
59
+ # transform emits the output hash directly (TODO.perf 19) —
60
+ # the __root__ Element tree and its to_hash flatten are gone.
61
+ return transformation.transform(instance, options)
66
62
  end
67
63
  end
68
64
 
@@ -140,15 +140,11 @@ options = {})
140
140
  nil_mapping = to_map[:nil]
141
141
 
142
142
  if nil_mapping == :empty || converted_from_nil_to_empty
143
- element = Lutaml::KeyValue::DataModel::Element.new(
144
- rule.serialized_name, []
145
- )
146
- parent.add_child(element)
143
+ puts " HNC-EMPTY #{rule.serialized_name}" if ENV["T19"]
144
+ parent[rule.serialized_name] = []
147
145
  elsif nil_mapping == :nil || converted_from_empty_to_nil
148
- element = Lutaml::KeyValue::DataModel::Element.new(
149
- rule.serialized_name, nil
150
- )
151
- parent.add_child(element)
146
+ puts " HNC-NIL #{rule.serialized_name}" if ENV["T19"]
147
+ parent[rule.serialized_name] = nil
152
148
  end
153
149
  # else: Skip nil collection (default behavior)
154
150
  end
@@ -158,8 +154,6 @@ options = {})
158
154
  key_attribute = root_mappings.key(:key)
159
155
  value_attribute = root_mappings.key(:value)
160
156
 
161
- parent.value ||= {} if parent.value.nil?
162
-
163
157
  if key_attribute && value_attribute
164
158
  handle_root_mappings_key_value(parent, items, key_attribute,
165
159
  value_attribute, rule, options)
@@ -213,7 +207,7 @@ options = {})
213
207
  else
214
208
  attr_value
215
209
  end
216
- parent.value[key_value.to_s] = serialized_value if serialized_value
210
+ parent[key_value.to_s] = serialized_value if serialized_value
217
211
  end
218
212
  end
219
213
 
@@ -336,7 +330,7 @@ options)
336
330
  end
337
331
  end
338
332
 
339
- parent.value[key_value.to_s] = item_hash unless item_hash.empty?
333
+ parent[key_value.to_s] = item_hash unless item_hash.empty?
340
334
  end
341
335
  end
342
336
 
@@ -362,10 +356,7 @@ options)
362
356
  end
363
357
  end
364
358
 
365
- element = Lutaml::KeyValue::DataModel::Element.new(
366
- rule.serialized_name, keyed_hash
367
- )
368
- parent.add_child(element)
359
+ parent[rule.serialized_name] = keyed_hash
369
360
  end
370
361
 
371
362
  # Find value attribute from child_mappings.
@@ -490,18 +481,11 @@ options)
490
481
 
491
482
  # Create an array collection element (default).
492
483
  def create_array_collection_element(parent, rule, items, options)
493
- coll_element = Lutaml::KeyValue::DataModel::Element.new(rule.serialized_name)
494
-
495
- if items.empty?
496
- coll_element.value = []
497
- else
498
- items.each do |item|
499
- child_value = create_value_for_item(rule, item, options)
500
- coll_element.add_child(child_value) unless child_value.nil?
501
- end
484
+ built = items.each_with_object([]) do |item, memo|
485
+ child_value = create_value_for_item(rule, item, options)
486
+ memo << child_value unless child_value.nil?
502
487
  end
503
-
504
- parent.add_child(coll_element)
488
+ parent[rule.serialized_name] = built
505
489
  end
506
490
 
507
491
  # Create a value element for a collection item.
@@ -554,8 +538,7 @@ options)
554
538
  def serialize_nested_model(value, attr_type, options)
555
539
  child_transformation = create_transformation(attr_type)
556
540
  child_root = child_transformation.transform(value, options)
557
- child_hash = child_root.to_hash
558
- child_hash["__root__"]
541
+ child_root.empty? ? nil : child_root
559
542
  end
560
543
 
561
544
  # Serialize a primitive value.
@@ -303,12 +303,10 @@ model_class: nil)
303
303
  # @param options [Hash] Serialization options
304
304
  # @return [Hash, nil] The serialized hash or nil if empty
305
305
  def transform_nested_model(value, child_transformation, options)
306
+ # transform emits the content hash directly (TODO.perf 19);
307
+ # nil for empty keeps render_nil: false working.
306
308
  child_root = child_transformation.transform(value, options)
307
- child_hash = child_root.to_hash
308
- result = child_hash["__root__"]
309
-
310
- # Return nil for empty hashes (allows render_nil: false to work)
311
- result.nil? || result.empty? ? nil : result
309
+ child_root.empty? ? nil : child_root
312
310
  end
313
311
 
314
312
  # Check if the rule defines a Reference type.
@@ -209,7 +209,10 @@ register = self.register)
209
209
  def transform(model_instance, options = {})
210
210
  # For key-value formats, we typically don't have a named root
211
211
  # Instead, we create an anonymous root that holds all attributes
212
- root = Lutaml::KeyValue::DataModel::Element.new("__root__")
212
+ # Hash-direct emission (TODO.perf 19): the __root__ Element
213
+ # tree was write-only in production — every consumer flattened
214
+ # it straight back to a hash. The output hash is built directly.
215
+ root = {}
213
216
 
214
217
  # Apply each compiled rule (with filtering support). A
215
218
  # when_attribute partition applies once for its whole key.
@@ -230,11 +233,7 @@ register = self.register)
230
233
 
231
234
  if ENV["DEBUG_KEYED_COLLECTION"]
232
235
  puts "=== TRANSFORM COMPLETE ==="
233
- puts "root.children.count: #{root.children.count}"
234
- root.children.each do |child|
235
- puts " child key=#{child.key.inspect}, value=#{child.value.inspect}, to_hash=#{child.to_hash.inspect}"
236
- end
237
- puts "root.to_hash: #{root.to_hash.inspect}"
236
+ puts "root: #{root.inspect}"
238
237
  end
239
238
 
240
239
  root
@@ -289,15 +288,9 @@ register = self.register)
289
288
  end
290
289
  return unless present
291
290
 
292
- element = Lutaml::KeyValue::DataModel::Element.new(
293
- group.first.serialized_name,
294
- )
295
- if items.empty?
296
- element.value = []
297
- else
298
- items.each { |item| element.add_child(item) }
299
- end
300
- parent.add_child(element)
291
+ # Items are stamped hashes or primitives: the tree's
292
+ # children_to_array semantics, expressed directly.
293
+ parent[group.first.serialized_name] = items
301
294
  end
302
295
 
303
296
  def stamp_discriminator(item, rule)
@@ -332,16 +325,20 @@ register = self.register)
332
325
  # Handle custom serialization methods (e.g., with: { to: ... })
333
326
  if rule.has_custom_methods? && rule.custom_methods[:to]
334
327
  to_method = rule.custom_methods[:to]
328
+ # Custom methods receive an Element "doc" and populate it
329
+ # (public contract, TODO.perf 19 keeps it): a scratch element
330
+ # is merged into the output hash afterwards.
331
+ doc = Lutaml::KeyValue::DataModel::Element.new("__custom__")
335
332
  # lutaml-model#550: custom methods may declare a third context
336
333
  # parameter to receive the options passed to `to_*`.
337
334
  if model_instance.method(to_method).parameters.size >= 3
338
- return model_instance.public_send(to_method, model_instance,
339
- parent, options[:context])
335
+ model_instance.public_send(to_method, model_instance,
336
+ doc, options[:context])
337
+ else
338
+ model_instance.public_send(to_method, model_instance, doc)
340
339
  end
341
-
342
- # Call custom method which directly modifies the parent element
343
- return model_instance.public_send(to_method, model_instance,
344
- parent)
340
+ parent.update(doc.to_hash.fetch("__custom__", {}))
341
+ return
345
342
  end
346
343
 
347
344
  # Handle delegation - extract value from delegated object
@@ -450,17 +447,9 @@ converted_from_empty_to_nil: false, converted_from_nil_to_empty: false)
450
447
  to_map = (rule.option(:value_map) || {})[:to] || {}
451
448
  nil_mapping = to_map[:nil]
452
449
  if nil_mapping == :empty || converted_from_nil_to_empty
453
- # Render as empty collection
454
- element = Lutaml::KeyValue::DataModel::Element.new(
455
- rule.serialized_name, []
456
- )
457
- parent.add_child(element)
450
+ parent[rule.serialized_name] = []
458
451
  elsif nil_mapping == :nil || converted_from_empty_to_nil
459
- # Render nil collection as nil value
460
- element = Lutaml::KeyValue::DataModel::Element.new(
461
- rule.serialized_name, nil
462
- )
463
- parent.add_child(element)
452
+ parent[rule.serialized_name] = nil
464
453
  else
465
454
  # Skip nil collection
466
455
  end
@@ -519,10 +508,10 @@ converted_from_empty_to_nil: false, converted_from_nil_to_empty: false)
519
508
  # Transform the value to a KeyValueElement
520
509
  value_root = value_transformation.transform(attr_value,
521
510
  options)
522
- value_hash = value_root.to_hash
523
511
 
524
- # Extract the value from the __root__ wrapper
525
- parent.value[key_value.to_s] = value_hash["__root__"]
512
+ # transform emits the output hash directly now
513
+ # (TODO.perf 19) — no __root__ wrapper to unwrap.
514
+ parent[key_value.to_s] = value_root
526
515
  next
527
516
  end
528
517
  end
@@ -540,7 +529,7 @@ converted_from_empty_to_nil: false, converted_from_nil_to_empty: false)
540
529
  # Get the child transformation for the collection item type
541
530
  if item_type.is_a?(Class) && item_type < Lutaml::Model::Serialize
542
531
  # Create a temporary parent KeyValueElement to hold the collection
543
- temp_parent = Lutaml::KeyValue::DataModel::Element.new("__temp__")
532
+ temp_parent = {}
544
533
 
545
534
  # Convert to array
546
535
  coll_items = Array(attr_value)
@@ -577,18 +566,15 @@ converted_from_empty_to_nil: false, converted_from_nil_to_empty: false)
577
566
  coll_items, options)
578
567
  end
579
568
 
580
- # Extract the value from temp_parent
581
- temp_hash = temp_parent.to_hash
582
- inner_value = temp_hash["__temp__"]
583
- coll_value = inner_value&.[](value_attribute.to_s) if inner_value
569
+ coll_value = temp_parent[value_attribute.to_s]
584
570
 
585
- parent.value[key_value.to_s] = coll_value
571
+ parent[key_value.to_s] = coll_value
586
572
  next
587
573
  end
588
574
  end
589
575
 
590
576
  # Fallback: just use the value directly
591
- parent.value[key_value.to_s] = attr_value
577
+ parent[key_value.to_s] = attr_value
592
578
  end
593
579
  # Case 2: Only key attribute specified - serialize all other attributes
594
580
  elsif key_attribute
@@ -662,7 +648,7 @@ converted_from_empty_to_nil: false, converted_from_nil_to_empty: false)
662
648
  end
663
649
  end
664
650
 
665
- parent.value[key_value.to_s] = item_hash unless item_hash.empty?
651
+ parent[key_value.to_s] = item_hash unless item_hash.empty?
666
652
  end
667
653
  end
668
654
 
@@ -872,17 +858,7 @@ child_mappings, options)
872
858
  puts " keyed_hash: #{keyed_hash.inspect}"
873
859
  end
874
860
 
875
- # Create element with hash value
876
- element = Lutaml::KeyValue::DataModel::Element.new(
877
- rule.serialized_name, keyed_hash
878
- )
879
-
880
- if ENV["DEBUG_KEYED_COLLECTION"]
881
- puts " element created: key=#{element.key.inspect}, value=#{element.value.inspect}, children.count=#{element.children.count}"
882
- puts " element.to_hash: #{element.to_hash.inspect}"
883
- end
884
-
885
- parent.add_child(element)
861
+ parent[rule.serialized_name] = keyed_hash
886
862
  end
887
863
 
888
864
  # Create an array collection element (default)
@@ -892,23 +868,13 @@ child_mappings, options)
892
868
  # @param items [Array] The collection items
893
869
  # @param options [Hash] Options
894
870
  def create_array_collection_element(parent, rule, items, options)
895
- # Create an element for the collection
896
- coll_element = Lutaml::KeyValue::DataModel::Element.new(rule.serialized_name)
897
-
898
- if items.empty?
899
- # For empty collections, set value to empty array explicitly
900
- coll_element.value = []
901
- else
902
- # Add each item as a child
903
- items.each do |item|
904
- child_value = create_value_for_item(rule, item, options)
905
- # Use explicit nil check - `if child_value` would fail for boolean false!
906
- coll_element.add_child(child_value) unless child_value.nil?
907
- end
871
+ # children_to_array semantics directly: always an array,
872
+ # nil children skipped (explicit nil check — booleans matter).
873
+ built = items.each_with_object([]) do |item, memo|
874
+ child_value = create_value_for_item(rule, item, options)
875
+ memo << child_value unless child_value.nil?
908
876
  end
909
-
910
- # Always add collection element
911
- parent.add_child(coll_element)
877
+ parent[rule.serialized_name] = built
912
878
  end
913
879
 
914
880
  # Get the key attribute for a keyed collection from child_mappings
@@ -956,9 +922,7 @@ child_mappings, options)
956
922
  # Since we don't have direct access to the parent mapping here,
957
923
  # serialize as array for now (default behavior)
958
924
  items.map do |item|
959
- child_root = child_transformation.transform(item, options)
960
- child_hash = child_root.to_hash
961
- child_hash["__root__"]
925
+ child_transformation.transform(item, options)
962
926
  end
963
927
  end
964
928
  else
@@ -970,10 +934,7 @@ child_mappings, options)
970
934
  mapping = attr_type.mappings_for(format, register_id)
971
935
  child_transformation = self.class.new(attr_type, mapping, format,
972
936
  register)
973
- child_root = child_transformation.transform(value,
974
- options)
975
- child_hash = child_root.to_hash
976
- child_hash["__root__"]
937
+ child_transformation.transform(value, options)
977
938
  # Use the transformation to serialize the nested model
978
939
  elsif attr_type.is_a?(Class) && attr_type < Lutaml::Model::Type::Value
979
940
  wrapped_value = attr_type.new(value)
@@ -1001,16 +962,10 @@ child_mappings, options)
1001
962
  when :omitted
1002
963
  return
1003
964
  when :nil
1004
- element = Lutaml::KeyValue::DataModel::Element.new(
1005
- rule.serialized_name, nil
1006
- )
1007
- parent.add_child(element)
965
+ parent[rule.serialized_name] = nil
1008
966
  return
1009
967
  when :empty
1010
- element = Lutaml::KeyValue::DataModel::Element.new(
1011
- rule.serialized_name, ""
1012
- )
1013
- parent.add_child(element)
968
+ parent[rule.serialized_name] = ""
1014
969
  return
1015
970
  end
1016
971
 
@@ -1019,16 +974,10 @@ child_mappings, options)
1019
974
  elsif Lutaml::Model::Utils.uninitialized?(value)
1020
975
  omitted_mapping = to_map[:omitted]
1021
976
  if omitted_mapping == :nil
1022
- element = Lutaml::KeyValue::DataModel::Element.new(
1023
- rule.serialized_name, nil
1024
- )
1025
- parent.add_child(element)
977
+ parent[rule.serialized_name] = nil
1026
978
  return
1027
979
  elsif omitted_mapping == :empty
1028
- element = Lutaml::KeyValue::DataModel::Element.new(
1029
- rule.serialized_name, ""
1030
- )
1031
- parent.add_child(element)
980
+ parent[rule.serialized_name] = ""
1032
981
  return
1033
982
  end
1034
983
  # uninitialized with no/omitted mapping → skip (default behavior)
@@ -1036,10 +985,7 @@ child_mappings, options)
1036
985
  elsif Lutaml::Model::Utils.empty?(value)
1037
986
  empty_mapping = to_map[:empty]
1038
987
  if empty_mapping == :nil
1039
- element = Lutaml::KeyValue::DataModel::Element.new(
1040
- rule.serialized_name, nil
1041
- )
1042
- parent.add_child(element)
988
+ parent[rule.serialized_name] = nil
1043
989
  return
1044
990
  elsif empty_mapping == :omitted
1045
991
  return
@@ -1050,12 +996,7 @@ child_mappings, options)
1050
996
  # Normal value serialization
1051
997
  child_value = create_value_for_item(rule, value, options)
1052
998
 
1053
- unless child_value.nil?
1054
- element = Lutaml::KeyValue::DataModel::Element.new(
1055
- rule.serialized_name, child_value
1056
- )
1057
- parent.add_child(element)
1058
- end
999
+ parent[rule.serialized_name] = child_value unless child_value.nil?
1059
1000
  end
1060
1001
 
1061
1002
  # Create value for an item (handles nested models and primitives)
@@ -1125,13 +1066,10 @@ child_mappings, options)
1125
1066
 
1126
1067
  if child_transformation
1127
1068
  child_root = child_transformation.transform(value, options)
1128
- # Return the hash representation of the child
1129
- # Remove the __root__ wrapper and return just the content
1130
- child_hash = child_root.to_hash
1131
- result = child_hash["__root__"]
1132
- # If the nested model serialized to empty hash, return nil
1133
- # This allows render_nil: false to work correctly for nested models
1134
- result.nil? || result.empty? ? nil : result
1069
+ # transform emits the content hash directly (TODO.perf 19);
1070
+ # an empty serialization still returns nil so render_nil:
1071
+ # false keeps working for nested models.
1072
+ child_root.empty? ? nil : child_root
1135
1073
  else
1136
1074
  # Fallback: serialize as primitive
1137
1075
  serialize_value(value, rule)
@@ -1167,15 +1105,12 @@ child_mappings, options)
1167
1105
  # For each key-value pair in the parsed content, add as a child element
1168
1106
  if parsed_content.is_a?(::Hash)
1169
1107
  parsed_content.each do |key, val|
1170
- element = Lutaml::KeyValue::DataModel::Element.new(key.to_s, val)
1171
- parent.add_child(element)
1108
+ parent[key.to_s] = val
1172
1109
  end
1173
1110
  elsif parsed_content.is_a?(::Array)
1174
- # If the parsed content is an array, we can't merge it directly
1175
- # This shouldn't happen with valid map_all usage, but handle it gracefully
1176
- element = Lutaml::KeyValue::DataModel::Element.new("__root__",
1177
- parsed_content)
1178
- parent.add_child(element)
1111
+ # Graceful handling of unusual map_all content (parity with
1112
+ # the tree's __root__ wrapper)
1113
+ parent["__root__"] = parsed_content
1179
1114
  end
1180
1115
  end
1181
1116
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.73"
5
+ VERSION = "0.8.74"
6
6
  end
7
7
  end
@@ -5,11 +5,15 @@ require "spec_helper"
5
5
  RSpec.describe Lutaml::KeyValue::Transformation::ValueSerializer do
6
6
  let(:format) { :json }
7
7
  let(:register_id) { :default }
8
+ # No doubles (house rule): a real transformation factory. Under
9
+ # hash-direct emission (TODO.perf 19) transform returns the content
10
+ # hash itself.
8
11
  let(:transformation_factory) do
9
- ->(_type_class) do
10
- double("Transformation",
11
- transform: double("Element",
12
- to_hash: { "__root__" => { "name" => "test" } }))
12
+ ->(type_class) do
13
+ Lutaml::KeyValue::Transformation.new(
14
+ type_class, type_class.mappings_for(format, register_id),
15
+ format, register_id
16
+ )
13
17
  end
14
18
  end
15
19
 
@@ -106,7 +110,7 @@ RSpec.describe Lutaml::KeyValue::Transformation::ValueSerializer do
106
110
  end
107
111
 
108
112
  it "validates type mismatch" do
109
- wrong_value = double("wrong_type")
113
+ wrong_value = Object.new
110
114
  expect do
111
115
  serializer.serialize_item(wrong_value, rule)
112
116
  end.to raise_error(Lutaml::Model::IncorrectModelError, /but should be/)
@@ -60,9 +60,7 @@ RSpec.describe Lutaml::KeyValue::Transformation do
60
60
 
61
61
  result = transformation.transform(model)
62
62
 
63
- expect(result).to be_a(Lutaml::KeyValue::DataModel::Element)
64
- expect(result.key).to eq("__root__")
65
- expect(result.children.length).to eq(3)
63
+ expect(result).to be_a(Hash)
66
64
  end
67
65
 
68
66
  it "creates proper key-value structure" do
@@ -71,7 +69,7 @@ RSpec.describe Lutaml::KeyValue::Transformation do
71
69
  transformation = described_class.new(KVSimpleModel, mapping, :json, nil)
72
70
 
73
71
  result = transformation.transform(model)
74
- hash = result.to_hash
72
+ hash = { "__root__" => result }
75
73
 
76
74
  # Remove __root__ wrapper
77
75
  expect(hash["__root__"]).to eq({
@@ -87,7 +85,7 @@ RSpec.describe Lutaml::KeyValue::Transformation do
87
85
  transformation = described_class.new(KVSimpleModel, mapping, :json, nil)
88
86
 
89
87
  result = transformation.transform(model)
90
- hash = result.to_hash
88
+ hash = { "__root__" => result }
91
89
 
92
90
  # nil values should be rendered by default
93
91
  expect(hash["__root__"]["age"]).to be_nil
@@ -103,7 +101,7 @@ RSpec.describe Lutaml::KeyValue::Transformation do
103
101
  transformation = described_class.new(KVPerson, mapping, :json, nil)
104
102
 
105
103
  result = transformation.transform(person)
106
- hash = result.to_hash
104
+ hash = { "__root__" => result }
107
105
 
108
106
  expect(hash["__root__"]).to eq({
109
107
  "name" => "John",
@@ -121,7 +119,7 @@ RSpec.describe Lutaml::KeyValue::Transformation do
121
119
  transformation = described_class.new(KVPerson, mapping, :json, nil)
122
120
 
123
121
  result = transformation.transform(person)
124
- hash = result.to_hash
122
+ hash = { "__root__" => result }
125
123
 
126
124
  # nil nested model should be in output (based on default render_nil)
127
125
  expect(hash["__root__"]["address"]).to be_nil
@@ -137,7 +135,7 @@ RSpec.describe Lutaml::KeyValue::Transformation do
137
135
  transformation = described_class.new(KVTeam, mapping, :json, nil)
138
136
 
139
137
  result = transformation.transform(team)
140
- hash = result.to_hash
138
+ hash = { "__root__" => result }
141
139
 
142
140
  expect(hash["__root__"]).to eq({
143
141
  "name" => "Dev Team",
@@ -152,7 +150,7 @@ RSpec.describe Lutaml::KeyValue::Transformation do
152
150
  transformation = described_class.new(KVTeam, mapping, :json, nil)
153
151
 
154
152
  result = transformation.transform(team)
155
- hash = result.to_hash
153
+ hash = { "__root__" => result }
156
154
 
157
155
  # Empty collections are not rendered by default (render_empty defaults to false)
158
156
  expect(hash["__root__"]["members"]).to be_nil
@@ -164,7 +162,7 @@ RSpec.describe Lutaml::KeyValue::Transformation do
164
162
  transformation = described_class.new(KVTeam, mapping, :json, nil)
165
163
 
166
164
  result = transformation.transform(team)
167
- hash = result.to_hash
165
+ hash = { "__root__" => result }
168
166
 
169
167
  # nil collection should be nil by default
170
168
  expect(hash["__root__"]["members"]).to be_nil
@@ -190,7 +188,7 @@ RSpec.describe Lutaml::KeyValue::Transformation do
190
188
  nil)
191
189
 
192
190
  result = transformation.transform(model)
193
- hash = result.to_hash
191
+ hash = { "__root__" => result }
194
192
 
195
193
  expect(hash["__root__"]["name"]).to eq("JOHN")
196
194
  end
@@ -213,7 +211,7 @@ RSpec.describe Lutaml::KeyValue::Transformation do
213
211
  transformation = described_class.new(KVRenderModel, mapping, :json, nil)
214
212
 
215
213
  result = transformation.transform(model)
216
- hash = result.to_hash
214
+ hash = { "__root__" => result }
217
215
 
218
216
  # optional should be omitted
219
217
  expect(hash["__root__"].keys).not_to include("optional")
@@ -225,7 +223,7 @@ RSpec.describe Lutaml::KeyValue::Transformation do
225
223
  transformation = described_class.new(KVRenderModel, mapping, :json, nil)
226
224
 
227
225
  result = transformation.transform(model)
228
- hash = result.to_hash
226
+ hash = { "__root__" => result }
229
227
 
230
228
  # defaulted should be omitted
231
229
  expect(hash["__root__"].keys).not_to include("defaulted")
@@ -234,22 +232,22 @@ RSpec.describe Lutaml::KeyValue::Transformation do
234
232
  end
235
233
 
236
234
  describe "architecture compliance" do
237
- it "produces OOP data structures, not raw hashes" do
235
+ it "emits the presentation hash directly (TODO.perf 19)" do
238
236
  model = KVSimpleModel.new(name: "John", age: 30, active: true)
239
237
  mapping = KVSimpleModel.mappings_for(:json)
240
238
  transformation = described_class.new(KVSimpleModel, mapping, :json, nil)
241
239
 
242
240
  result = transformation.transform(model)
243
241
 
244
- # Result should be KeyValueElement, not Hash
245
- expect(result).to be_a(Lutaml::KeyValue::DataModel::Element)
246
- expect(result).not_to be_a(Hash)
247
-
248
- # Children should also be KeyValueElements
249
- expect(result.children).to all(be_a(Lutaml::KeyValue::DataModel::Element))
242
+ # Hash-direct emission: the __root__ Element tree (write-only in
243
+ # production, immediately flattened by every consumer) is gone;
244
+ # transform produces the output hash itself.
245
+ expect(result).to be_a(Hash)
246
+ expect(result).to eq("name" => "John", "age" => 30, "active" => true)
250
247
  end
251
248
 
252
249
  it "separates content (KeyValueElement) from presentation (Hash)" do
250
+ skip "superseded by hash-direct emission (TODO.perf 19)"
253
251
  model = KVSimpleModel.new(name: "John", age: 30, active: true)
254
252
  mapping = KVSimpleModel.mappings_for(:json)
255
253
  transformation = described_class.new(KVSimpleModel, mapping, :json, nil)
@@ -259,7 +257,7 @@ RSpec.describe Lutaml::KeyValue::Transformation do
259
257
  expect(result).to be_a(Lutaml::KeyValue::DataModel::Element)
260
258
 
261
259
  # Presentation happens via to_hash, not during transformation
262
- hash = result.to_hash
260
+ hash = { "__root__" => result }
263
261
  expect(hash).to be_a(Hash)
264
262
  end
265
263
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.73
4
+ version: 0.8.74
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-27 00:00:00.000000000 Z
11
+ date: 2026-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64