lutaml-model 0.8.19 → 0.8.23

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 (139) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-tests.yml +1 -1
  3. data/.github/workflows/rake.yml +1 -0
  4. data/.rubocop.yml +1 -1
  5. data/.rubocop_todo.yml +133 -72
  6. data/bench/bench_common.rb +8 -5
  7. data/bench/bench_niso.rb +1 -2
  8. data/bench/gate_config.rb +4 -1
  9. data/benchmark/serialization_benchmark.rb +4 -4
  10. data/docs/_guides/value-transformations.adoc +27 -0
  11. data/docs/_guides/xml-mapping.adoc +31 -0
  12. data/lib/compat/opal/generate_boot.rb +3 -1
  13. data/lib/compat/opal/lutaml_model_boot.rb +10 -7
  14. data/lib/compat/opal/moxml_boot.rb +6 -1
  15. data/lib/lutaml/hash_format/format.rb +29 -0
  16. data/lib/lutaml/hash_format.rb +5 -27
  17. data/lib/lutaml/json/adapter/oj_adapter.rb +7 -0
  18. data/lib/lutaml/json/adapter/standard_adapter.rb +17 -5
  19. data/lib/lutaml/json/format.rb +61 -0
  20. data/lib/lutaml/json/generator_options.rb +62 -0
  21. data/lib/lutaml/json.rb +4 -59
  22. data/lib/lutaml/jsonl/adapter/standard_adapter.rb +1 -1
  23. data/lib/lutaml/jsonl/format.rb +22 -0
  24. data/lib/lutaml/jsonl.rb +5 -19
  25. data/lib/lutaml/jsonld/adapter.rb +12 -4
  26. data/lib/lutaml/key_value/adapter/json/oj_adapter.rb +7 -0
  27. data/lib/lutaml/key_value/adapter/json/standard_adapter.rb +17 -5
  28. data/lib/lutaml/key_value/adapter/jsonl/standard_adapter.rb +1 -1
  29. data/lib/lutaml/key_value/format.rb +26 -0
  30. data/lib/lutaml/key_value/transform.rb +4 -1
  31. data/lib/lutaml/key_value/transformation/value_serializer.rb +0 -2
  32. data/lib/lutaml/key_value.rb +4 -21
  33. data/lib/lutaml/model/attribute.rb +38 -4
  34. data/lib/lutaml/model/collection.rb +7 -2
  35. data/lib/lutaml/model/compiled_rule.rb +1 -1
  36. data/lib/lutaml/model/config.rb +2 -2
  37. data/lib/lutaml/model/consolidation_map.rb +2 -2
  38. data/lib/lutaml/model/consolidation_rule/dispatch_block.rb +2 -2
  39. data/lib/lutaml/model/errors.rb +2 -2
  40. data/lib/lutaml/model/global_context.rb +4 -2
  41. data/lib/lutaml/model/mapping/mapping_rule.rb +4 -1
  42. data/lib/lutaml/model/mapping/model_mapping.rb +2 -2
  43. data/lib/lutaml/model/runtime_compatibility.rb +2 -2
  44. data/lib/lutaml/model/schema/class_loader.rb +2 -1
  45. data/lib/lutaml/model/schema/decorators/definition_collection.rb +4 -4
  46. data/lib/lutaml/model/schema/definitions/namespace.rb +2 -1
  47. data/lib/lutaml/model/schema/file_writer.rb +5 -2
  48. data/lib/lutaml/model/schema/renderers/base.rb +4 -3
  49. data/lib/lutaml/model/schema/renderers/mappings.rb +9 -3
  50. data/lib/lutaml/model/schema/renderers/member_decls.rb +9 -3
  51. data/lib/lutaml/model/schema/renderers/model.rb +18 -6
  52. data/lib/lutaml/model/schema/renderers/required_files_calculator.rb +2 -1
  53. data/lib/lutaml/model/schema/renderers.rb +2 -1
  54. data/lib/lutaml/model/schema/rnc_compiler/source_resolver.rb +6 -2
  55. data/lib/lutaml/model/schema/rnc_compiler.rb +3 -1
  56. data/lib/lutaml/model/schema/rng_compiler/element_visitor.rb +35 -15
  57. data/lib/lutaml/model/schema/rng_compiler/value_type_resolver.rb +4 -2
  58. data/lib/lutaml/model/schema/rng_compiler.rb +10 -5
  59. data/lib/lutaml/model/schema/xml_compiler/element_order.rb +2 -1
  60. data/lib/lutaml/model/schema/xml_compiler/spec_builder/complex_types.rb +4 -2
  61. data/lib/lutaml/model/schema/xml_compiler/spec_builder/members.rb +39 -13
  62. data/lib/lutaml/model/schema/xml_compiler/spec_builder.rb +29 -10
  63. data/lib/lutaml/model/schema/xml_compiler/supported_data_types.rb +2 -2
  64. data/lib/lutaml/model/schema/xml_compiler.rb +10 -5
  65. data/lib/lutaml/model/sequence.rb +4 -4
  66. data/lib/lutaml/model/serialization_adapter.rb +1 -1
  67. data/lib/lutaml/model/serialize/attribute_definition.rb +1 -0
  68. data/lib/lutaml/model/serialize/format_conversion.rb +40 -9
  69. data/lib/lutaml/model/serialize/initialization.rb +9 -1
  70. data/lib/lutaml/model/serialize/model_import.rb +1 -0
  71. data/lib/lutaml/model/serialize.rb +22 -3
  72. data/lib/lutaml/model/transformation.rb +5 -0
  73. data/lib/lutaml/model/type/uninitialized_class_guard.rb +1 -1
  74. data/lib/lutaml/model/type.rb +9 -0
  75. data/lib/lutaml/model/type_registry.rb +2 -2
  76. data/lib/lutaml/model/uninitialized_class.rb +1 -1
  77. data/lib/lutaml/model/union.rb +5 -2
  78. data/lib/lutaml/model/utils.rb +2 -2
  79. data/lib/lutaml/model/value_policy.rb +27 -0
  80. data/lib/lutaml/model/version.rb +1 -1
  81. data/lib/lutaml/model.rb +17 -16
  82. data/lib/lutaml/rdf/mapping.rb +2 -2
  83. data/lib/lutaml/rdf/namespace_set.rb +2 -2
  84. data/lib/lutaml/rdf/transform.rb +2 -2
  85. data/lib/lutaml/toml/format.rb +48 -0
  86. data/lib/lutaml/toml.rb +5 -46
  87. data/lib/lutaml/xml/adapter/base_adapter.rb +2 -2
  88. data/lib/lutaml/xml/builder/base.rb +2 -2
  89. data/lib/lutaml/xml/format.rb +331 -0
  90. data/lib/lutaml/xml/mapping.rb +26 -2
  91. data/lib/lutaml/xml/mapping_rule.rb +62 -3
  92. data/lib/lutaml/xml/model_transform.rb +183 -55
  93. data/lib/lutaml/xml/namespace_collector.rb +3 -3
  94. data/lib/lutaml/xml/parse_session.rb +59 -0
  95. data/lib/lutaml/xml/parsed_namespace_set.rb +2 -2
  96. data/lib/lutaml/xml/schema/builder.rb +5 -5
  97. data/lib/lutaml/xml/schema/xsd/schema_location_mapping.rb +2 -2
  98. data/lib/lutaml/xml/schema/xsd.rb +0 -2
  99. data/lib/lutaml/xml/schema/xsd_schema.rb +2 -1
  100. data/lib/lutaml/xml/serialization/format_conversion.rb +3 -3
  101. data/lib/lutaml/xml/transformation/element_builder.rb +2 -1
  102. data/lib/lutaml/xml/transformation/ordered_applier.rb +2 -1
  103. data/lib/lutaml/xml/transformation.rb +29 -0
  104. data/lib/lutaml/xml/xml_element.rb +2 -0
  105. data/lib/lutaml/xml.rb +4 -328
  106. data/lib/lutaml/yaml/format.rb +30 -0
  107. data/lib/lutaml/yaml.rb +5 -28
  108. data/lib/lutaml/yamls/adapter/mapping.rb +2 -2
  109. data/lib/lutaml/yamls/format.rb +22 -0
  110. data/lib/lutaml/yamls.rb +5 -19
  111. data/lib/tasks/benchmark_runner.rb +2 -2
  112. data/lutaml-model.gemspec +1 -1
  113. data/spec/lutaml/json/adapter/standard_adapter_spec.rb +159 -0
  114. data/spec/lutaml/jsonld/adapter_spec.rb +19 -0
  115. data/spec/lutaml/model/attribute_apply_value_map_spec.rb +26 -13
  116. data/spec/lutaml/model/attribute_spec.rb +8 -3
  117. data/spec/lutaml/model/collection_spec.rb +1 -1
  118. data/spec/lutaml/model/collection_state_nesting_spec.rb +44 -0
  119. data/spec/lutaml/model/custom_bibtex_adapter_spec.rb +2 -2
  120. data/spec/lutaml/model/custom_from_empty_values_spec.rb +74 -0
  121. data/spec/lutaml/model/custom_vobject_adapter_spec.rb +8 -8
  122. data/spec/lutaml/model/defaults_spec.rb +45 -0
  123. data/spec/lutaml/model/generator_defaults_spec.rb +101 -0
  124. data/spec/lutaml/model/json_spec.rb +1 -2
  125. data/spec/lutaml/model/json_state_nesting_spec.rb +82 -0
  126. data/spec/lutaml/model/serialize/builder_spec.rb +6 -3
  127. data/spec/lutaml/model/store_spec.rb +3 -1
  128. data/spec/lutaml/model/transform_apply_value_map_spec.rb +6 -3
  129. data/spec/lutaml/model/union_attribute_spec.rb +4 -2
  130. data/spec/lutaml/model/value_policy_spec.rb +82 -0
  131. data/spec/lutaml/require_load_order_spec.rb +25 -0
  132. data/spec/lutaml/xml/attribute_element_namespace_disjoint_spec.rb +88 -0
  133. data/spec/lutaml/xml/attribute_namespace_disjointness_spec.rb +125 -0
  134. data/spec/lutaml/xml/out_of_namespace_parsing_spec.rb +123 -0
  135. data/spec/lutaml/xml/schema/rnc_compiler_spec.rb +17 -6
  136. data/spec/lutaml/xml/schema/rng_compiler_spec.rb +15 -6
  137. data/spec/lutaml/xml/shared_element_rules_spec.rb +64 -0
  138. data/spec/lutaml/xml/w3c_types_spec.rb +3 -6
  139. metadata +26 -4
@@ -34,6 +34,10 @@ if RUBY_ENGINE == "opal"
34
34
  require "moxml/doctype"
35
35
  require "moxml/entity_reference"
36
36
  require "moxml/entity_registry"
37
+ require "moxml/entity"
38
+ require "moxml/entity/restorer"
39
+ require "moxml/entity/reference"
40
+ require "moxml/xml_emitter"
37
41
  require "moxml/adapter"
38
42
  require "moxml/adapter/base"
39
43
  require "moxml/adapter/customized_rexml"
@@ -46,10 +50,11 @@ if RUBY_ENGINE == "opal"
46
50
  require "moxml/sax/namespace_splitter"
47
51
  require "moxml/adapter/rexml"
48
52
  require "moxml/adapter/customized_oga"
53
+ require "moxml/adapter/customized_oga/entity_decoder"
54
+ require "moxml/adapter/customized_oga/raw_value_override"
49
55
  require "moxml/adapter/customized_oga/xml_declaration"
50
56
  require "moxml/adapter/customized_oga/xml_generator"
51
57
  require "moxml/adapter/oga"
52
- require "moxml/document_builder"
53
58
  require "moxml/builder"
54
59
  require "moxml/context"
55
60
  require "moxml/config"
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Internal Hash format implementation. Loaded from lib/lutaml/hash_format.rb
4
+ # (the public entrypoint) and from lib/lutaml/model.rb (so the model
5
+ # entrypoint triggers the registration side effects).
6
+ # No require_relative "model" here — keeps the require graph acyclic.
7
+
8
+ module Lutaml
9
+ module HashFormat
10
+ class Error < StandardError; end
11
+
12
+ autoload :Adapter, "#{__dir__}/adapter"
13
+ end
14
+ end
15
+
16
+ Lutaml::Model::FormatRegistry.register(
17
+ :hash,
18
+ mapping_class: Lutaml::HashFormat::Adapter::Mapping,
19
+ adapter_class: Lutaml::HashFormat::Adapter::StandardAdapter,
20
+ transformer: Lutaml::HashFormat::Adapter::Transform,
21
+ key_value: true,
22
+ adapter_options: {
23
+ available: %i[standard standard_hash],
24
+ default: :standard,
25
+ },
26
+ )
27
+
28
+ require_relative "type/serializers"
29
+ Lutaml::HashFormat::Type::Serializers.register_all!
@@ -1,29 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Hash format module
4
- # Provides Lutaml::HashFormat namespace for Hash serialization
5
-
6
- module Lutaml
7
- module HashFormat
8
- class Error < StandardError; end
9
-
10
- autoload :Adapter, "#{__dir__}/hash_format/adapter"
11
- end
12
- end
13
-
14
- # Register Hash format with the format registry
15
- Lutaml::Model::FormatRegistry.register(
16
- :hash,
17
- mapping_class: Lutaml::HashFormat::Adapter::Mapping,
18
- adapter_class: Lutaml::HashFormat::Adapter::StandardAdapter,
19
- transformer: Lutaml::HashFormat::Adapter::Transform,
20
- key_value: true,
21
- adapter_options: {
22
- available: %i[standard standard_hash],
23
- default: :standard,
24
- },
25
- )
26
-
27
- # Register Hash type serializers
28
- require_relative "hash_format/type/serializers"
29
- Lutaml::HashFormat::Type::Serializers.register_all!
3
+ # Public Hash format entrypoint. Loads the base model and the internal
4
+ # implementation. The internal file does not require the model back,
5
+ # which keeps the require graph acyclic.
6
+ require_relative "model"
7
+ require_relative "hash_format/format"
@@ -17,6 +17,13 @@ module Lutaml
17
17
  end
18
18
 
19
19
  # rubocop:disable Style/ArgumentsForwarding -- anonymous * requires Ruby 3.2+
20
+ # Oj has its own option namespace and its own escape_mode. Handing it
21
+ # the stdlib generator's options overrides that configuration, so it
22
+ # takes none of them -- which is what it received before.
23
+ def ignores_generator_options?
24
+ true
25
+ end
26
+
20
27
  def to_json(*args)
21
28
  require "oj"
22
29
  # Handle KeyValueElement input (new symmetric architecture)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require_relative "../generator_options"
4
5
 
5
6
  module Lutaml
6
7
  module Json
@@ -9,12 +10,16 @@ module Lutaml
9
10
  FORMAT_SYMBOL = :json
10
11
 
11
12
  def self.parse(json, _options = {})
12
- JSON.parse(json, create_additions: false)
13
+ JSON.parse(json)
13
14
  end
14
15
 
15
- def to_json(*args)
16
- options = args.first || {}
16
+ # This adapter hands its payload to the stdlib generator, so a
17
+ # JSON::State carrying the outer formatting is meaningful here.
18
+ def accepts_generator_state?
19
+ true
20
+ end
17
21
 
22
+ def to_json(*args)
18
23
  # Handle KeyValueElement input (new symmetric architecture)
19
24
  attributes_to_serialize = if @attributes.is_a?(Lutaml::KeyValue::DataModel::Element)
20
25
  # Unwrap __root__ wrapper to get actual content
@@ -24,10 +29,17 @@ module Lutaml
24
29
  @attributes
25
30
  end
26
31
 
32
+ unless GeneratorOptions.lutaml_options?(args.first)
33
+ return JSON.generate(attributes_to_serialize, args.first)
34
+ end
35
+
36
+ options = args.first || {}
37
+ generator_options = GeneratorOptions.filter(options)
38
+
27
39
  if options[:pretty]
28
- JSON.pretty_generate(attributes_to_serialize, *args)
40
+ JSON.pretty_generate(attributes_to_serialize, generator_options)
29
41
  else
30
- JSON.generate(attributes_to_serialize, *args)
42
+ JSON.generate(attributes_to_serialize, generator_options)
31
43
  end
32
44
  end
33
45
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Internal JSON format implementation. Loaded from lib/lutaml/json.rb
4
+ # (the public entrypoint) and from lib/lutaml/model.rb (so the model
5
+ # entrypoint triggers the registration side effects).
6
+ # No require_relative "model" here — keeps the require graph acyclic.
7
+
8
+ require "lutaml/model/runtime_compatibility"
9
+ require_relative "../key_value/format"
10
+
11
+ module Lutaml
12
+ module Json
13
+ class Error < StandardError; end
14
+
15
+ require_relative "adapter/document"
16
+ require_relative "adapter/mapping"
17
+ require_relative "adapter/mapping_rule"
18
+ require_relative "adapter/transform"
19
+ require_relative "adapter/standard_adapter"
20
+ Lutaml::Model::RuntimeCompatibility.require_native(
21
+ "#{__dir__}/adapter/oj_adapter",
22
+ "#{__dir__}/adapter/multi_json_adapter",
23
+ )
24
+ require_relative "schema"
25
+
26
+ # Convenience aliases for common classes at the module level
27
+ # Allows Lutaml::Json::Mapping to resolve to Lutaml::Json::Adapter::Mapping
28
+ def self.const_missing(name)
29
+ if Adapter.const_defined?(name, false)
30
+ Adapter.const_get(name, false)
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ # Detect available JSON adapters
37
+ def self.detect_adapter
38
+ return :standard if defined?(::JSON) && Lutaml::Model.opal?
39
+ return :oj if defined?(::Oj)
40
+ return :multi_json if defined?(::MultiJson)
41
+ return :standard if defined?(::JSON)
42
+
43
+ nil
44
+ end
45
+ end
46
+ end
47
+
48
+ Lutaml::Model::FormatRegistry.register(
49
+ :json,
50
+ mapping_class: Lutaml::Json::Adapter::Mapping,
51
+ adapter_class: Lutaml::Json::Adapter::StandardAdapter,
52
+ transformer: Lutaml::Json::Adapter::Transform,
53
+ key_value: true,
54
+ adapter_options: {
55
+ available: %i[standard standard_json multi_json oj],
56
+ default: :standard,
57
+ },
58
+ )
59
+
60
+ require_relative "type/serializers"
61
+ Lutaml::Json::Type::Serializers.register_all!
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Lutaml
6
+ module Json
7
+ # Filters the options LutaML threads through serialization down to the
8
+ # subset the JSON generator accepts.
9
+ #
10
+ # json 2.x silently ignored unknown generator options; json 3.0 raises
11
+ # ArgumentError on them, so LutaML's own options (:register, :pretty and
12
+ # any caller-supplied extras) have to be stripped before they reach
13
+ # JSON.generate.
14
+ module GeneratorOptions
15
+ # Options the generator accepts on both json 2.x and 3.x. Measured
16
+ # against 2.9.1, 2.15.2, 2.19.9, 2.20.0, 2.21.1, 2.21.2 and 3.0.0.
17
+ # :escape_slash is NOT here because json 3.0 removed it; on 2.x it still
18
+ # arrives via DERIVED_PERMITTED. :allow_duplicate_key IS here because the
19
+ # generator accepts it on every version while JSON::State never exposed
20
+ # it as an accessor, so the derived half alone would miss it.
21
+ BASE_PERMITTED = %i[
22
+ allow_duplicate_key allow_nan array_nl as_json ascii_only
23
+ buffer_initial_length depth indent max_nesting object_nl script_safe
24
+ sort_keys space space_before strict
25
+ ].freeze
26
+
27
+ # Additive, so a json release that adds an option needs no change here.
28
+ # Opal's JSON shim has no State class, hence the guard.
29
+ DERIVED_PERMITTED =
30
+ if defined?(::JSON::State)
31
+ accessors = ::JSON::State.instance_methods(false).grep(/\A[a-z_][a-z0-9_]*=\z/)
32
+ accessors.map { |name| name.to_s.chomp("=").to_sym }
33
+ else
34
+ []
35
+ end.freeze
36
+
37
+ PERMITTED = (BASE_PERMITTED | DERIVED_PERMITTED).freeze
38
+
39
+ # json 3.0 removed :escape_slash, which was only ever an alias of
40
+ # :script_safe. Dropping it would silently stop escaping slashes, so it
41
+ # is translated instead of discarded.
42
+ RENAMED = { escape_slash: :script_safe }.freeze
43
+
44
+ def self.filter(options)
45
+ return {} unless options.is_a?(::Hash)
46
+
47
+ options.each_with_object({}) do |(key, value), kept|
48
+ key = RENAMED.fetch(key, key) unless PERMITTED.include?(key)
49
+ kept[key] = value if PERMITTED.include?(key)
50
+ end
51
+ end
52
+
53
+ # Ruby's JSON generator calls #to_json with a JSON::State whenever a
54
+ # document is nested inside another JSON.generate call. That is the
55
+ # generator's own state, not a LutaML options hash, and json 3.0 removed
56
+ # JSON::State#[], so it must never be read like one.
57
+ def self.lutaml_options?(argument)
58
+ argument.nil? || argument.is_a?(::Hash)
59
+ end
60
+ end
61
+ end
62
+ end
data/lib/lutaml/json.rb CHANGED
@@ -1,62 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # JSON format module
4
- # Provides Lutaml::Json namespace for JSON serialization
5
-
6
- require "lutaml/model/runtime_compatibility"
3
+ # Public JSON format entrypoint. Loads the base model and the internal
4
+ # implementation. The internal file does not require the model back,
5
+ # which keeps the require graph acyclic.
7
6
  require_relative "model"
8
- require_relative "key_value"
9
-
10
- module Lutaml
11
- module Json
12
- class Error < StandardError; end
13
-
14
- require_relative "json/adapter/document"
15
- require_relative "json/adapter/mapping"
16
- require_relative "json/adapter/mapping_rule"
17
- require_relative "json/adapter/transform"
18
- require_relative "json/adapter/standard_adapter"
19
- Lutaml::Model::RuntimeCompatibility.require_native(
20
- "#{__dir__}/json/adapter/oj_adapter",
21
- "#{__dir__}/json/adapter/multi_json_adapter",
22
- )
23
- require_relative "json/schema"
24
-
25
- # Convenience aliases for common classes at the module level
26
- # Allows Lutaml::Json::Mapping to resolve to Lutaml::Json::Adapter::Mapping
27
- def self.const_missing(name)
28
- if Adapter.const_defined?(name, false)
29
- Adapter.const_get(name, false)
30
- else
31
- super
32
- end
33
- end
34
-
35
- # Detect available JSON adapters
36
- def self.detect_adapter
37
- return :standard if defined?(::JSON) && Lutaml::Model.opal?
38
- return :oj if defined?(::Oj)
39
- return :multi_json if defined?(::MultiJson)
40
- return :standard if defined?(::JSON)
41
-
42
- nil
43
- end
44
- end
45
- end
46
-
47
- # Register JSON format with the format registry
48
- Lutaml::Model::FormatRegistry.register(
49
- :json,
50
- mapping_class: Lutaml::Json::Adapter::Mapping,
51
- adapter_class: Lutaml::Json::Adapter::StandardAdapter,
52
- transformer: Lutaml::Json::Adapter::Transform,
53
- key_value: true,
54
- adapter_options: {
55
- available: %i[standard standard_json multi_json oj],
56
- default: :standard,
57
- },
58
- )
59
-
60
- # Register JSON type serializers
61
- require_relative "json/type/serializers"
62
- Lutaml::Json::Type::Serializers.register_all!
7
+ require_relative "json/format"
@@ -14,7 +14,7 @@ module Lutaml
14
14
  next if line.strip.empty?
15
15
 
16
16
  begin
17
- results << JSON.parse(line, create_additions: false)
17
+ results << JSON.parse(line)
18
18
  rescue JSON::ParserError => e
19
19
  warn "Skipping invalid line: #{e.message}"
20
20
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Internal JSONL format implementation. Loaded from lib/lutaml/jsonl.rb
4
+ # (the public entrypoint) and from lib/lutaml/model.rb (so the model
5
+ # entrypoint triggers the registration side effects).
6
+ # No require_relative "model" here — keeps the require graph acyclic.
7
+
8
+ module Lutaml
9
+ module Jsonl
10
+ class Error < StandardError; end
11
+
12
+ autoload :Adapter, "#{__dir__}/adapter"
13
+ end
14
+ end
15
+
16
+ Lutaml::Model::FormatRegistry.register(
17
+ :jsonl,
18
+ mapping_class: Lutaml::Jsonl::Adapter::Mapping,
19
+ adapter_class: Lutaml::Jsonl::Adapter::StandardAdapter,
20
+ transformer: Lutaml::Jsonl::Adapter::Transform,
21
+ key_value: true,
22
+ )
data/lib/lutaml/jsonl.rb CHANGED
@@ -1,21 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # JSONL format module
4
- # Provides Lutaml::Jsonl namespace for JSONL serialization
5
-
6
- module Lutaml
7
- module Jsonl
8
- class Error < StandardError; end
9
-
10
- autoload :Adapter, "#{__dir__}/jsonl/adapter"
11
- end
12
- end
13
-
14
- # Register JSONL format with the format registry
15
- Lutaml::Model::FormatRegistry.register(
16
- :jsonl,
17
- mapping_class: Lutaml::Jsonl::Adapter::Mapping,
18
- adapter_class: Lutaml::Jsonl::Adapter::StandardAdapter,
19
- transformer: Lutaml::Jsonl::Adapter::Transform,
20
- key_value: true,
21
- )
3
+ # Public JSONL format entrypoint. Loads the base model and the internal
4
+ # implementation. The internal file does not require the model back,
5
+ # which keeps the require graph acyclic.
6
+ require_relative "model"
7
+ require_relative "jsonl/format"
@@ -1,21 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require_relative "../json/generator_options"
4
5
 
5
6
  module Lutaml
6
7
  module JsonLd
7
8
  class Adapter < Lutaml::KeyValue::Document
8
9
  def self.parse(jsonld_string, _options = {})
9
- JSON.parse(jsonld_string, create_additions: false)
10
+ JSON.parse(jsonld_string)
10
11
  end
11
12
 
12
13
  def to_jsonld(*args)
13
- options = args.first || {}
14
14
  data = @attributes
15
+
16
+ unless Lutaml::Json::GeneratorOptions.lutaml_options?(args.first)
17
+ return JSON.generate(data, args.first)
18
+ end
19
+
20
+ options = args.first || {}
21
+ generator_options = Lutaml::Json::GeneratorOptions.filter(options)
22
+
15
23
  if options[:pretty]
16
- JSON.pretty_generate(data, *args)
24
+ JSON.pretty_generate(data, generator_options)
17
25
  else
18
- JSON.generate(data, *args)
26
+ JSON.generate(data, generator_options)
19
27
  end
20
28
  end
21
29
  end
@@ -21,6 +21,13 @@ module Lutaml
21
21
  end
22
22
 
23
23
  # rubocop:disable Style/ArgumentsForwarding -- anonymous * requires Ruby 3.2+
24
+ # Oj has its own option namespace and its own escape_mode. Handing it
25
+ # the stdlib generator's options overrides that configuration, so it
26
+ # takes none of them -- which is what it received before.
27
+ def ignores_generator_options?
28
+ true
29
+ end
30
+
24
31
  def to_json(*args)
25
32
  require "oj"
26
33
  # Handle KeyValueElement input (new symmetric architecture)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require_relative "../../../json/generator_options"
4
5
 
5
6
  # Backward compatibility - delegates to Lutaml::Json::Adapter
6
7
  # @deprecated Use Lutaml::Json::Adapter::StandardAdapter instead
@@ -13,12 +14,16 @@ module Lutaml
13
14
  FORMAT_SYMBOL = :json
14
15
 
15
16
  def self.parse(json, _options = {})
16
- JSON.parse(json, create_additions: false)
17
+ JSON.parse(json)
17
18
  end
18
19
 
19
- def to_json(*args)
20
- options = args.first || {}
20
+ # This adapter hands its payload to the stdlib generator, so a
21
+ # JSON::State carrying the outer formatting is meaningful here.
22
+ def accepts_generator_state?
23
+ true
24
+ end
21
25
 
26
+ def to_json(*args)
22
27
  # Handle KeyValueElement input (new symmetric architecture)
23
28
  attributes_to_serialize = if @attributes.is_a?(Lutaml::KeyValue::DataModel::Element)
24
29
  # Unwrap __root__ wrapper to get actual content
@@ -28,10 +33,17 @@ module Lutaml
28
33
  @attributes
29
34
  end
30
35
 
36
+ unless Lutaml::Json::GeneratorOptions.lutaml_options?(args.first)
37
+ return JSON.generate(attributes_to_serialize, args.first)
38
+ end
39
+
40
+ options = args.first || {}
41
+ generator_options = Lutaml::Json::GeneratorOptions.filter(options)
42
+
31
43
  if options[:pretty]
32
- JSON.pretty_generate(attributes_to_serialize, *args)
44
+ JSON.pretty_generate(attributes_to_serialize, generator_options)
33
45
  else
34
- JSON.generate(attributes_to_serialize, *args)
46
+ JSON.generate(attributes_to_serialize, generator_options)
35
47
  end
36
48
  end
37
49
  end
@@ -15,7 +15,7 @@ module Lutaml
15
15
  next if line.strip.empty?
16
16
 
17
17
  begin
18
- results << JSON.parse(line, create_additions: false)
18
+ results << JSON.parse(line)
19
19
  rescue JSON::ParserError => e
20
20
  warn "Skipping invalid line: #{e.message}"
21
21
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Internal KeyValue implementation. Loaded from lib/lutaml/key_value.rb
4
+ # (the public entrypoint) and from lib/lutaml/model.rb (so the model
5
+ # entrypoint triggers the registration side effects).
6
+ # No require_relative "model" here — keeps the require graph acyclic.
7
+
8
+ module Lutaml
9
+ module KeyValue
10
+ autoload :DataModel, "#{__dir__}/data_model"
11
+ autoload :Document, "#{__dir__}/document"
12
+ autoload :Mapping, "#{__dir__}/mapping"
13
+ autoload :MappingRule, "#{__dir__}/mapping_rule"
14
+ autoload :Transform, "#{__dir__}/transform"
15
+ autoload :Transformation, "#{__dir__}/transformation"
16
+ autoload :TransformationBuilder, "#{__dir__}/transformation_builder"
17
+ autoload :Adapter, "#{__dir__}/adapter"
18
+ end
19
+ end
20
+
21
+ # Register KeyValue transformation builders for all key-value formats
22
+ %i[json yaml toml hash].each do |format|
23
+ Lutaml::Model::TransformationRegistry.register_builder(
24
+ format, Lutaml::KeyValue::TransformationBuilder
25
+ )
26
+ end
@@ -249,7 +249,10 @@ format)
249
249
  value = apply_value_map(value, rule.value_map(:from, options), attr)
250
250
 
251
251
  if rule.has_custom_method_for_deserialization?
252
- return unless Lutaml::Model::Utils.present?(value)
252
+ # An empty value ("", [], {}) is a present value per the
253
+ # missing-values semantics and must reach the custom method;
254
+ # only nil (non-existent) skips it (lutaml-model#746).
255
+ return if value.nil?
253
256
 
254
257
  return rule.deserialize(instance, value, attributes, model_class)
255
258
  end
@@ -88,11 +88,9 @@ model_class: nil)
88
88
  # @param rule [CompiledRule] The compiled rule
89
89
  # @return [Boolean] true if the rule defines a nested model
90
90
  def nested_model?(rule)
91
- # rubocop:disable Style/IncludeOrExtend
92
91
  # Use include? instead of < because < returns nil (not false) for non-subclasses
93
92
  rule.attribute_type.is_a?(Class) &&
94
93
  rule.attribute_type.include?(Lutaml::Model::Serialize)
95
- # rubocop:enable Style/IncludeOrExtend
96
94
  end
97
95
 
98
96
  # Serialize a nested model to a hash representation.
@@ -1,24 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Lutaml
4
- module KeyValue
5
- autoload :DataModel, "#{__dir__}/key_value/data_model"
6
- autoload :Document, "#{__dir__}/key_value/document"
7
- autoload :Mapping, "#{__dir__}/key_value/mapping"
8
- autoload :MappingRule, "#{__dir__}/key_value/mapping_rule"
9
- autoload :Transform, "#{__dir__}/key_value/transform"
10
- autoload :Transformation, "#{__dir__}/key_value/transformation"
11
- autoload :TransformationBuilder,
12
- "#{__dir__}/key_value/transformation_builder"
13
- autoload :Adapter, "#{__dir__}/key_value/adapter"
14
- end
15
- end
16
-
3
+ # Public KeyValue entrypoint. Loads the base model and the internal
4
+ # implementation. The internal file does not require the model back,
5
+ # which keeps the require graph acyclic.
17
6
  require_relative "model"
18
-
19
- # Register KeyValue transformation builders for all key-value formats
20
- %i[json yaml toml hash].each do |format|
21
- Lutaml::Model::TransformationRegistry.register_builder(
22
- format, Lutaml::KeyValue::TransformationBuilder
23
- )
24
- end
7
+ require_relative "key_value/format"