lutaml-model 0.6.6 → 0.7.1

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-repos-todo.json +7 -0
  3. data/.github/workflows/dependent-repos.json +17 -8
  4. data/.rubocop_todo.yml +18 -33
  5. data/README.adoc +4380 -2557
  6. data/lib/lutaml/model/attribute.rb +94 -15
  7. data/lib/lutaml/model/choice.rb +7 -0
  8. data/lib/lutaml/model/comparable_model.rb +48 -9
  9. data/lib/lutaml/model/error/collection_count_out_of_range_error.rb +1 -1
  10. data/lib/lutaml/model/error/polymorphic_error.rb +9 -0
  11. data/lib/lutaml/model/error.rb +1 -0
  12. data/lib/lutaml/model/mapping/json_mapping.rb +17 -0
  13. data/lib/lutaml/model/{key_value_mapping.rb → mapping/key_value_mapping.rb} +58 -14
  14. data/lib/lutaml/model/{key_value_mapping_rule.rb → mapping/key_value_mapping_rule.rb} +18 -2
  15. data/lib/lutaml/model/mapping/mapping_rule.rb +299 -0
  16. data/lib/lutaml/model/mapping/toml_mapping.rb +25 -0
  17. data/lib/lutaml/model/{xml_mapping.rb → mapping/xml_mapping.rb} +97 -15
  18. data/lib/lutaml/model/{xml_mapping_rule.rb → mapping/xml_mapping_rule.rb} +20 -3
  19. data/lib/lutaml/model/mapping/yaml_mapping.rb +17 -0
  20. data/lib/lutaml/model/mapping.rb +14 -0
  21. data/lib/lutaml/model/schema/xml_compiler.rb +15 -15
  22. data/lib/lutaml/model/sequence.rb +2 -2
  23. data/lib/lutaml/model/serialize.rb +249 -98
  24. data/lib/lutaml/model/type/date.rb +1 -1
  25. data/lib/lutaml/model/type/date_time.rb +2 -2
  26. data/lib/lutaml/model/type/hash.rb +1 -1
  27. data/lib/lutaml/model/type/time.rb +2 -2
  28. data/lib/lutaml/model/type/time_without_date.rb +2 -2
  29. data/lib/lutaml/model/uninitialized_class.rb +64 -0
  30. data/lib/lutaml/model/utils.rb +14 -0
  31. data/lib/lutaml/model/validation.rb +1 -0
  32. data/lib/lutaml/model/version.rb +1 -1
  33. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +1 -1
  34. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
  35. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +1 -1
  36. data/lib/lutaml/model/xml_adapter/xml_document.rb +38 -17
  37. data/lib/lutaml/model/xml_adapter/xml_element.rb +17 -7
  38. data/lib/lutaml/model.rb +1 -0
  39. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +3 -3
  40. data/spec/fixtures/person.rb +5 -5
  41. data/spec/lutaml/model/attribute_spec.rb +37 -1
  42. data/spec/lutaml/model/cdata_spec.rb +6 -6
  43. data/spec/lutaml/model/collection_spec.rb +52 -4
  44. data/spec/lutaml/model/comparable_model_spec.rb +92 -27
  45. data/spec/lutaml/model/custom_model_spec.rb +3 -3
  46. data/spec/lutaml/model/custom_serialization_spec.rb +6 -6
  47. data/spec/lutaml/model/defaults_spec.rb +1 -1
  48. data/spec/lutaml/model/enum_spec.rb +1 -1
  49. data/spec/lutaml/model/group_spec.rb +316 -14
  50. data/spec/lutaml/model/key_value_mapping_spec.rb +41 -3
  51. data/spec/lutaml/model/multiple_mapping_spec.rb +8 -8
  52. data/spec/lutaml/model/polymorphic_spec.rb +348 -0
  53. data/spec/lutaml/model/render_empty_spec.rb +194 -0
  54. data/spec/lutaml/model/render_nil_spec.rb +206 -22
  55. data/spec/lutaml/model/simple_model_spec.rb +9 -9
  56. data/spec/lutaml/model/value_map_spec.rb +240 -0
  57. data/spec/lutaml/model/xml/namespace/nested_with_explicit_namespace_spec.rb +85 -0
  58. data/spec/lutaml/model/xml/xml_element_spec.rb +93 -0
  59. data/spec/lutaml/model/xml_mapping_rule_spec.rb +102 -2
  60. data/spec/lutaml/model/xml_mapping_spec.rb +45 -3
  61. data/spec/sample_model_spec.rb +3 -3
  62. metadata +20 -8
  63. data/lib/lutaml/model/mapping_rule.rb +0 -109
@@ -3,10 +3,8 @@ require_relative "xml_adapter"
3
3
  require_relative "config"
4
4
  require_relative "type"
5
5
  require_relative "attribute"
6
- require_relative "mapping_rule"
7
6
  require_relative "mapping_hash"
8
- require_relative "xml_mapping"
9
- require_relative "key_value_mapping"
7
+ require_relative "mapping"
10
8
  require_relative "json_adapter"
11
9
  require_relative "comparable_model"
12
10
  require_relative "schema_location"
@@ -94,22 +92,15 @@ module Lutaml
94
92
  end
95
93
  end
96
94
 
97
- # Define an attribute for the model
98
- def attribute(name, type, options = {})
99
- if type.is_a?(Hash)
100
- options[:method_name] = type[:method]
101
- type = nil
102
- end
103
-
104
- attr = Attribute.new(name, type, options)
105
- attributes[name] = attr
95
+ def define_attribute_methods(attr)
96
+ name = attr.name
106
97
 
107
98
  if attr.enum?
108
99
  add_enum_methods_to_model(
109
100
  model,
110
101
  name,
111
- options[:values],
112
- collection: options[:collection],
102
+ attr.options[:values],
103
+ collection: attr.options[:collection],
113
104
  )
114
105
  elsif attr.derived? && name != attr.method_name
115
106
  define_method(name) do
@@ -119,35 +110,74 @@ module Lutaml
119
110
  define_method(name) do
120
111
  instance_variable_get(:"@#{name}")
121
112
  end
122
-
123
113
  define_method(:"#{name}=") do |value|
124
114
  value_set_for(name)
125
115
  instance_variable_set(:"@#{name}", attr.cast_value(value))
126
116
  end
127
117
  end
118
+ end
119
+
120
+ # Define an attribute for the model
121
+ def attribute(name, type, options = {})
122
+ if type.is_a?(Hash)
123
+ options[:method_name] = type[:method]
124
+ type = nil
125
+ end
126
+
127
+ attr = Attribute.new(name, type, options)
128
+ attributes[name] = attr
129
+ define_attribute_methods(attr)
128
130
 
129
131
  attr
130
132
  end
131
133
 
132
134
  def root?
133
- mappings_for(:xml).root?
135
+ mappings_for(:xml)&.root?
136
+ end
137
+
138
+ def import_model_with_root_error(model)
139
+ return unless model.mappings.key?(:xml) && model.root?
140
+
141
+ raise Lutaml::Model::ImportModelWithRootError.new(model)
134
142
  end
135
143
 
136
144
  def import_model_attributes(model)
137
- raise Lutaml::Model::ImportModelWithRootError.new(model) if model.root?
145
+ model.attributes.each_value do |attr|
146
+ define_attribute_methods(attr)
147
+ end
138
148
 
139
- @attributes.merge!(model.attributes)
149
+ @choice_attributes.concat(Utils.deep_dup(model.choice_attributes))
150
+ @attributes.merge!(Utils.deep_dup(model.attributes))
140
151
  end
141
152
 
142
153
  def import_model_mappings(model)
143
- raise Lutaml::Model::ImportModelWithRootError.new(model) if model.root?
154
+ import_model_with_root_error(model)
155
+
156
+ Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
157
+ next unless model.mappings.key?(format)
158
+
159
+ mapping = model.mappings_for(format)
160
+ mapping = Utils.deep_dup(mapping)
161
+
162
+ @mappings[format] ||= format == :xml ? XmlMapping.new : KeyValueMapping.new
163
+
164
+ if format == :xml
165
+ @mappings[format].merge_mapping_attributes(mapping)
166
+ @mappings[format].merge_mapping_elements(mapping)
167
+ @mappings[format].merge_elements_sequence(mapping)
168
+ else
169
+ @mappings[format].mappings.concat(mapping.mappings)
170
+ end
171
+ end
172
+ end
144
173
 
145
- @mappings.merge!(model.mappings)
174
+ def handle_key_value_mappings(mapping, format)
175
+ @mappings[format] ||= KeyValueMapping.new
176
+ @mappings[format].mappings.concat(mapping.mappings)
146
177
  end
147
178
 
148
179
  def import_model(model)
149
- raise Lutaml::Model::ImportModelWithRootError.new(model) if model.root?
150
-
180
+ import_model_with_root_error(model)
151
181
  import_model_attributes(model)
152
182
  import_model_mappings(model)
153
183
  end
@@ -213,7 +243,8 @@ module Lutaml
213
243
 
214
244
  def add_enum_setter_if_not_defined(klass, enum_name, _values, collection)
215
245
  Utils.add_method_if_not_defined(klass, "#{enum_name}=") do |value|
216
- value = [value] unless value.is_a?(Array)
246
+ value = [] if value.nil?
247
+ value = [value] if !value.is_a?(Array)
217
248
 
218
249
  value_set_for(enum_name)
219
250
 
@@ -233,8 +264,8 @@ module Lutaml
233
264
 
234
265
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
235
266
  define_method(format) do |&block|
236
- klass = format == :xml ? XmlMapping : KeyValueMapping
237
- mappings[format] ||= klass.new
267
+ mapping_class = const_get("Lutaml::Model::#{format.to_s.capitalize}Mapping")
268
+ mappings[format] ||= mapping_class.new
238
269
  mappings[format].instance_eval(&block)
239
270
 
240
271
  if format == :xml && !mappings[format].root_element && !mappings[format].no_root?
@@ -243,6 +274,8 @@ module Lutaml
243
274
  end
244
275
 
245
276
  define_method(:"from_#{format}") do |data, options = {}|
277
+ return data if Utils.uninitialized?(data)
278
+
246
279
  adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
247
280
 
248
281
  doc = adapter.parse(data, options)
@@ -260,7 +293,7 @@ module Lutaml
260
293
  options[:encoding] = doc.encoding
261
294
  apply_mappings(doc, format, options)
262
295
  else
263
- apply_mappings(doc.to_h, format)
296
+ apply_mappings(doc.to_h, format, options)
264
297
  end
265
298
  end
266
299
 
@@ -298,7 +331,7 @@ module Lutaml
298
331
 
299
332
  def key_value(&block)
300
333
  Lutaml::Model::Config::KEY_VALUE_FORMATS.each do |format|
301
- mappings[format] ||= KeyValueMapping.new
334
+ mappings[format] ||= KeyValueMapping.new(format)
302
335
  mappings[format].instance_eval(&block)
303
336
  end
304
337
  end
@@ -310,43 +343,68 @@ module Lutaml
310
343
 
311
344
  mappings.each_with_object({}) do |rule, hash|
312
345
  name = rule.to
313
- next if except&.include?(name) || (only && !only.include?(name))
314
- next if !rule.custom_methods[:to] && (!rule.render_default? && instance.using_default?(rule.to))
346
+ attr = attributes[name]
315
347
 
348
+ next if except&.include?(name) || (only && !only.include?(name))
316
349
  next handle_delegate(instance, rule, hash, format) if rule.delegate
317
350
 
318
351
  if rule.custom_methods[:to]
319
352
  next instance.send(rule.custom_methods[:to], instance, hash)
320
353
  end
321
354
 
322
- value = instance.send(name)
355
+ value = rule.serialize(instance)
323
356
 
324
357
  if rule.raw_mapping?
325
358
  adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
326
359
  return adapter.parse(value, options)
327
360
  end
328
361
 
329
- attribute = attributes[name]
330
-
331
- if export_method = rule.transform[:export] || attribute.transform_export_method
362
+ if export_method = rule.transform[:export] || attr.transform_export_method
332
363
  value = export_method.call(value)
333
364
  end
334
365
 
335
- next hash.merge!(generate_hash_from_child_mappings(attribute, value, format, rule.root_mappings)) if rule.root_mapping?
366
+ next hash.merge!(generate_hash_from_child_mappings(attr, value, format, rule.root_mappings)) if rule.root_mapping?
336
367
 
337
368
  value = if rule.child_mappings
338
- generate_hash_from_child_mappings(attribute, value, format, rule.child_mappings)
369
+ generate_hash_from_child_mappings(attr, value, format, rule.child_mappings)
339
370
  else
340
- attribute.serialize(value, format, options)
371
+ attr.serialize(value, format, options)
341
372
  end
342
373
 
343
- next unless rule.render?(value)
374
+ next if !rule.render?(value, instance, options)
375
+
376
+ value = apply_value_map(value, rule.value_map(:to, options), attr)
344
377
 
345
378
  rule_from_name = rule.multiple_mappings? ? rule.from.first.to_s : rule.from.to_s
346
379
  hash[rule_from_name] = value
347
380
  end
348
381
  end
349
382
 
383
+ def apply_value_map(value, value_map, attr)
384
+ if value.nil?
385
+ value_for_option(value_map[:nil], attr)
386
+ elsif Utils.empty?(value)
387
+ value_for_option(value_map[:empty], attr, value)
388
+ elsif Utils.uninitialized?(value)
389
+ value_for_option(value_map[:omitted], attr)
390
+ else
391
+ value
392
+ end
393
+ end
394
+
395
+ def value_for_option(option, attr, empty_value = nil)
396
+ return nil if option == :nil
397
+ return empty_value || empty_object(attr) if option == :empty
398
+
399
+ Lutaml::Model::UninitializedClass.instance
400
+ end
401
+
402
+ def empty_object(attr)
403
+ return [] if attr.collection?
404
+
405
+ ""
406
+ end
407
+
350
408
  def handle_delegate(instance, rule, hash, format)
351
409
  name = rule.to
352
410
  value = instance.send(rule.delegate).send(name)
@@ -362,9 +420,9 @@ module Lutaml
362
420
  end
363
421
 
364
422
  def default_mappings(format)
365
- klass = format == :xml ? XmlMapping : KeyValueMapping
423
+ _mappings = format == :xml ? XmlMapping.new : KeyValueMapping.new(format)
366
424
 
367
- klass.new.tap do |mapping|
425
+ _mappings.tap do |mapping|
368
426
  attributes&.each_key do |name|
369
427
  mapping.map_element(
370
428
  name.to_s,
@@ -441,7 +499,7 @@ module Lutaml
441
499
  attr_value
442
500
  end
443
501
 
444
- next unless mapping_rule&.render?(attr_value)
502
+ next unless mapping_rule&.render?(attr_value, nil)
445
503
 
446
504
  if path == :key
447
505
  map_key = attr_value
@@ -484,46 +542,43 @@ module Lutaml
484
542
  instance = options[:instance] || model.new
485
543
  return instance if Utils.blank?(doc)
486
544
 
487
- options[:mappings] = mappings_for(format).mappings
545
+ mappings = mappings_for(format)
488
546
 
547
+ if mappings.polymorphic_mapping
548
+ return resolve_polymorphic(doc, format, mappings, instance, options)
549
+ end
550
+
551
+ options[:mappings] = mappings.mappings
489
552
  return apply_xml_mapping(doc, instance, options) if format == :xml
490
553
 
491
554
  apply_hash_mapping(doc, instance, format, options)
492
555
  end
493
556
 
557
+ def resolve_polymorphic(doc, format, mappings, instance, options = {})
558
+ polymorphic_mapping = mappings.polymorphic_mapping
559
+ return instance if polymorphic_mapping.polymorphic_map.empty?
560
+
561
+ klass_key = doc[polymorphic_mapping.name]
562
+ klass_name = polymorphic_mapping.polymorphic_map[klass_key]
563
+ klass = Object.const_get(klass_name)
564
+
565
+ klass.apply_mappings(doc, format, options)
566
+ end
567
+
494
568
  def apply_xml_mapping(doc, instance, options = {})
495
- options = Utils.deep_dup(options)
569
+ options = prepare_options(options)
496
570
  instance.encoding = options[:encoding]
497
571
  return instance unless doc
498
572
 
499
- if options[:default_namespace].nil?
500
- options[:default_namespace] = mappings_for(:xml)&.namespace_uri
501
- end
502
573
  mappings = options[:mappings] || mappings_for(:xml).mappings
503
574
 
504
- raise Lutaml::Model::CollectionTrueMissingError(self, option[:caller_class]) if doc.is_a?(Array)
575
+ validate_document!(doc, options)
505
576
 
506
- doc_order = doc.root.order
507
- if instance.respond_to?(:ordered=)
508
- instance.element_order = doc_order
509
- instance.ordered = mappings_for(:xml).ordered? || options[:ordered]
510
- instance.mixed = mappings_for(:xml).mixed_content? || options[:mixed_content]
511
- end
512
-
513
- schema_location = doc.attributes.values.find do |a|
514
- a.unprefixed_name == "schemaLocation"
515
- end
516
-
517
- if !schema_location.nil?
518
- instance.schema_location = Lutaml::Model::SchemaLocation.new(
519
- schema_location: schema_location.value,
520
- prefix: schema_location.namespace_prefix,
521
- namespace: schema_location.namespace,
522
- )
523
- end
577
+ set_instance_ordering(instance, doc, options)
578
+ set_schema_location(instance, doc)
524
579
 
525
580
  defaults_used = []
526
- validate_sequence!(doc_order)
581
+ validate_sequence!(doc.root.order)
527
582
 
528
583
  mappings.each do |rule|
529
584
  raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
@@ -531,26 +586,76 @@ module Lutaml
531
586
  attr = attribute_for_rule(rule)
532
587
  next if attr&.derived?
533
588
 
589
+ new_opts = options.dup
590
+ if rule.namespace_set?
591
+ new_opts[:default_namespace] = rule.namespace
592
+ end
593
+
534
594
  value = if rule.raw_mapping?
535
595
  doc.root.inner_xml
536
596
  elsif rule.content_mapping?
537
597
  rule.cdata ? doc.cdata : doc.text
538
- elsif val = value_for_rule(doc, rule, options, instance)
539
- val
540
- elsif instance.using_default?(rule.to) || rule.render_default
541
- defaults_used << rule.to
542
- attr&.default || rule.to_value_for(instance)
598
+ else
599
+ val = value_for_rule(doc, rule, new_opts, instance)
600
+
601
+ if (Utils.uninitialized?(val) || val.nil?) && (instance.using_default?(rule.to) || rule.render_default)
602
+ defaults_used << rule.to
603
+ attr&.default || rule.to_value_for(instance)
604
+ else
605
+ val
606
+ end
543
607
  end
544
608
 
545
- value = normalize_xml_value(value, rule, attr, options)
609
+ value = apply_value_map(value, rule.value_map(:from, new_opts), attr)
610
+ value = normalize_xml_value(value, rule, attr, new_opts)
546
611
  rule.deserialize(instance, value, attributes, self)
547
612
  end
548
613
 
549
- defaults_used.each { |attr_name| instance.using_default_for(attr_name) }
614
+ defaults_used.each do |attr_name|
615
+ instance.using_default_for(attr_name)
616
+ end
550
617
 
551
618
  instance
552
619
  end
553
620
 
621
+ def prepare_options(options)
622
+ opts = Utils.deep_dup(options)
623
+ opts[:default_namespace] ||= mappings_for(:xml)&.namespace_uri
624
+
625
+ opts
626
+ end
627
+
628
+ def validate_document!(doc, options)
629
+ return unless doc.is_a?(Array)
630
+
631
+ raise Lutaml::Model::CollectionTrueMissingError(
632
+ self,
633
+ options[:caller_class],
634
+ )
635
+ end
636
+
637
+ def set_instance_ordering(instance, doc, options)
638
+ return unless instance.respond_to?(:ordered=)
639
+
640
+ instance.element_order = doc.root.order
641
+ instance.ordered = mappings_for(:xml).ordered? || options[:ordered]
642
+ instance.mixed = mappings_for(:xml).mixed_content? || options[:mixed_content]
643
+ end
644
+
645
+ def set_schema_location(instance, doc)
646
+ schema_location = doc.attributes.values.find do |a|
647
+ a.unprefixed_name == "schemaLocation"
648
+ end
649
+
650
+ return if schema_location.nil?
651
+
652
+ instance.schema_location = Lutaml::Model::SchemaLocation.new(
653
+ schema_location: schema_location.value,
654
+ prefix: schema_location.namespace_prefix,
655
+ namespace: schema_location.namespace,
656
+ )
657
+ end
658
+
554
659
  def value_for_rule(doc, rule, options, instance)
555
660
  rule_names = rule.namespaced_names(options[:default_namespace])
556
661
 
@@ -563,43 +668,73 @@ module Lutaml
563
668
  end
564
669
 
565
670
  if rule.using_custom_methods? || attr.type == Lutaml::Model::Type::Hash
566
- return children.first
671
+ return_child = attr.type == Lutaml::Model::Type::Hash || !attr.collection? if attr
672
+ return return_child ? children.first : children
567
673
  end
568
674
 
675
+ return handle_cdata(children) if rule.cdata
676
+
677
+ values = []
678
+
569
679
  if Utils.present?(children)
570
680
  instance.value_set_for(attr.name)
681
+ else
682
+ children = nil
683
+ values = Lutaml::Model::UninitializedClass.instance
571
684
  end
572
685
 
573
- if rule.cdata
574
- values = children.map do |child|
575
- child.cdata_children&.map(&:text)
576
- end.flatten
577
- return children.count > 1 ? values : values.first
578
- end
579
-
580
- values = children.map do |child|
686
+ children&.each do |child|
581
687
  if !rule.using_custom_methods? && attr.type <= Serialize
582
- attr.cast(child, :xml, options.except(:mappings))
688
+ cast_options = options.except(:mappings)
689
+ cast_options[:polymorphic] = rule.polymorphic if rule.polymorphic
690
+
691
+ values << attr.cast(child, :xml, cast_options)
583
692
  elsif attr.raw?
584
- inner_xml_of(child)
693
+ values << inner_xml_of(child)
585
694
  else
586
- child&.children&.first&.text
695
+ return nil if rule.render_nil_as_nil? && child.nil_element?
696
+
697
+ text = child.nil_element? ? nil : (child&.text&.+ child&.cdata)
698
+ values << text
587
699
  end
588
700
  end
589
- attr&.collection? ? values : values.first
701
+
702
+ normalized_value_for_attr(values, attr)
590
703
  end
591
704
  end
592
705
 
706
+ def handle_cdata(children)
707
+ values = children.map do |child|
708
+ child.cdata_children&.map(&:text)
709
+ end.flatten
710
+
711
+ children.count > 1 ? values : values.first
712
+ end
713
+
714
+ def normalized_value_for_attr(values, attr)
715
+ # for xml collection true cases like
716
+ # <store><items /></store>
717
+ # <store><items xsi:nil="true"/></store>
718
+ # <store><items></items></store>
719
+ #
720
+ # these are considered empty collection
721
+ return [] if attr&.collection? && [[nil], [""]].include?(values)
722
+ return values if attr&.collection?
723
+
724
+ values.is_a?(Array) ? values.first : values
725
+ end
726
+
593
727
  def apply_hash_mapping(doc, instance, format, options = {})
594
728
  mappings = options[:mappings] || mappings_for(format).mappings
595
729
  mappings.each do |rule|
596
730
  raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
597
731
 
598
732
  attr = attribute_for_rule(rule)
733
+ next if attr&.derived?
599
734
 
600
735
  names = rule.multiple_mappings? ? rule.name : [rule.name]
601
736
 
602
- value = names.collect do |rule_name|
737
+ values = names.collect do |rule_name|
603
738
  if rule.root_mapping?
604
739
  doc
605
740
  elsif rule.raw_mapping?
@@ -609,10 +744,16 @@ module Lutaml
609
744
  doc[rule_name.to_s]
610
745
  elsif doc.key?(rule_name.to_sym)
611
746
  doc[rule_name.to_sym]
612
- else
747
+ elsif attr&.default_set?
613
748
  attr&.default
749
+ else
750
+ Lutaml::Model::UninitializedClass.instance
614
751
  end
615
- end.compact.first
752
+ end.compact
753
+
754
+ value = values.find { |v| Utils.initialized?(v) } || values.first
755
+
756
+ value = apply_value_map(value, rule.value_map(:from, options), attr)
616
757
 
617
758
  if rule.using_custom_methods?
618
759
  if Utils.present?(value)
@@ -623,7 +764,11 @@ module Lutaml
623
764
  end
624
765
 
625
766
  value = translate_mappings(value, rule.hash_mappings, attr, format)
626
- value = attr.cast(value, format) unless rule.hash_mappings
767
+
768
+ cast_options = {}
769
+ cast_options[:polymorphic] = rule.polymorphic if rule.polymorphic
770
+
771
+ value = attr.cast(value, format, cast_options) unless rule.hash_mappings
627
772
  attr.valid_collection!(value, self)
628
773
 
629
774
  rule.deserialize(instance, value, attributes, self)
@@ -633,7 +778,7 @@ module Lutaml
633
778
  end
634
779
 
635
780
  def normalize_xml_value(value, rule, attr, options = {})
636
- value = [value].compact if attr&.collection? && !value.is_a?(Array)
781
+ value = [value].compact if attr&.collection? && !value.is_a?(Array) && !value.nil?
637
782
 
638
783
  return value unless cast_value?(attr, rule)
639
784
 
@@ -701,7 +846,7 @@ module Lutaml
701
846
  attr_accessor :element_order, :schema_location, :encoding
702
847
  attr_writer :ordered, :mixed
703
848
 
704
- def initialize(attrs = {})
849
+ def initialize(attrs = {}, options = {})
705
850
  @using_default = {}
706
851
 
707
852
  return unless self.class.attributes
@@ -720,22 +865,28 @@ module Lutaml
720
865
 
721
866
  value = if attrs.key?(name) || attrs.key?(name.to_s)
722
867
  attr_value(attrs, name, attr)
723
- else
868
+ elsif attr.default_set?
724
869
  using_default_for(name)
725
870
  attr.default
871
+ else
872
+ Lutaml::Model::UninitializedClass.instance
726
873
  end
727
874
 
728
- # Initialize collections with an empty array if no value is provided
729
- if attr.collection? && value.nil?
730
- value = []
731
- end
732
-
733
875
  default = using_default?(name)
876
+ value = self.class.apply_value_map(value, value_map(options), attr)
734
877
  public_send(:"#{name}=", self.class.ensure_utf8(value))
735
878
  using_default_for(name) if default
736
879
  end
737
880
  end
738
881
 
882
+ def value_map(options)
883
+ {
884
+ omitted: options[:omitted] || :nil,
885
+ nil: options[:nil] || :nil,
886
+ empty: options[:empty] || :empty,
887
+ }
888
+ end
889
+
739
890
  def attr_value(attrs, name, attr_rule)
740
891
  value = if attrs.key?(name.to_sym)
741
892
  attrs[name.to_sym]
@@ -746,7 +897,7 @@ module Lutaml
746
897
  end
747
898
 
748
899
  if attr_rule.collection? || value.is_a?(Array)
749
- (value || []).map do |v|
900
+ value&.map do |v|
750
901
  if v.is_a?(Hash)
751
902
  attr_rule.type.new(v)
752
903
  else
@@ -3,7 +3,7 @@ module Lutaml
3
3
  module Type
4
4
  class Date < Value
5
5
  def self.cast(value)
6
- return nil if value.nil?
6
+ return value if value.nil? || Utils.uninitialized?(value)
7
7
 
8
8
  case value
9
9
  when ::DateTime, ::Time
@@ -6,7 +6,7 @@ module Lutaml
6
6
  # Date and time representation
7
7
  class DateTime < Value
8
8
  def self.cast(value)
9
- return nil if value.nil?
9
+ return value if value.nil? || Utils.uninitialized?(value)
10
10
 
11
11
  case value
12
12
  when ::DateTime then value
@@ -18,7 +18,7 @@ module Lutaml
18
18
  end
19
19
 
20
20
  def self.serialize(value)
21
- return nil if value.nil?
21
+ return value if value.nil? || Utils.uninitialized?(value)
22
22
 
23
23
  cast(value)&.iso8601
24
24
  end
@@ -3,7 +3,7 @@ module Lutaml
3
3
  module Type
4
4
  class Hash < Value
5
5
  def self.cast(value)
6
- return nil if value.nil?
6
+ return value if value.nil? || Utils.uninitialized?(value)
7
7
 
8
8
  hash = if value.respond_to?(:to_h)
9
9
  value.to_h
@@ -5,7 +5,7 @@ module Lutaml
5
5
  module Type
6
6
  class Time < Value
7
7
  def self.cast(value)
8
- return nil if value.nil?
8
+ return value if value.nil? || Utils.uninitialized?(value)
9
9
 
10
10
  case value
11
11
  when ::Time then value
@@ -17,7 +17,7 @@ module Lutaml
17
17
  end
18
18
 
19
19
  def self.serialize(value)
20
- return nil if value.nil?
20
+ return value if value.nil? || Utils.uninitialized?(value)
21
21
 
22
22
  value = cast(value)
23
23
  # value&.strftime("%Y-%m-%dT%H:%M:%S%:z")
@@ -11,7 +11,7 @@ module Lutaml
11
11
  # ::Time.new(1, 1, 1, time.hour, time.min, time.sec)
12
12
 
13
13
  def self.cast(value)
14
- return nil if value.nil?
14
+ return value if value.nil? || Utils.uninitialized?(value)
15
15
 
16
16
  case value
17
17
  when ::Time then value
@@ -22,7 +22,7 @@ module Lutaml
22
22
  end
23
23
 
24
24
  def self.serialize(value)
25
- return nil if value.nil?
25
+ return value if value.nil? || Utils.uninitialized?(value)
26
26
 
27
27
  value = cast(value)
28
28
  value.strftime("%H:%M:%S") # Format as HH:MM:SS