lutaml-model 0.4.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +36 -20
  3. data/README.adoc +1003 -192
  4. data/lib/lutaml/model/attribute.rb +6 -2
  5. data/lib/lutaml/model/error/collection_true_missing_error.rb +16 -0
  6. data/lib/lutaml/model/error/multiple_mappings_error.rb +6 -0
  7. data/lib/lutaml/model/error.rb +2 -0
  8. data/lib/lutaml/model/key_value_mapping.rb +25 -4
  9. data/lib/lutaml/model/key_value_mapping_rule.rb +16 -3
  10. data/lib/lutaml/model/loggable.rb +15 -0
  11. data/lib/lutaml/model/mapping_rule.rb +14 -2
  12. data/lib/lutaml/model/serialize.rb +114 -64
  13. data/lib/lutaml/model/type/decimal.rb +5 -0
  14. data/lib/lutaml/model/version.rb +1 -1
  15. data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +1 -0
  16. data/lib/lutaml/model/xml_adapter/builder/oga.rb +180 -0
  17. data/lib/lutaml/model/xml_adapter/builder/ox.rb +1 -0
  18. data/lib/lutaml/model/xml_adapter/oga/document.rb +20 -0
  19. data/lib/lutaml/model/xml_adapter/oga/element.rb +117 -0
  20. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +77 -44
  21. data/lib/lutaml/model/xml_adapter/xml_document.rb +14 -12
  22. data/lib/lutaml/model/xml_mapping.rb +3 -0
  23. data/lib/lutaml/model/xml_mapping_rule.rb +13 -4
  24. data/lib/lutaml/model.rb +1 -0
  25. data/spec/address_spec.rb +1 -0
  26. data/spec/fixtures/sample_model.rb +7 -0
  27. data/spec/lutaml/model/custom_model_spec.rb +47 -1
  28. data/spec/lutaml/model/included_spec.rb +192 -0
  29. data/spec/lutaml/model/mixed_content_spec.rb +48 -32
  30. data/spec/lutaml/model/multiple_mapping_spec.rb +329 -0
  31. data/spec/lutaml/model/ordered_content_spec.rb +1 -1
  32. data/spec/lutaml/model/render_nil_spec.rb +3 -0
  33. data/spec/lutaml/model/root_mappings_spec.rb +297 -0
  34. data/spec/lutaml/model/serializable_spec.rb +42 -7
  35. data/spec/lutaml/model/type/boolean_spec.rb +62 -0
  36. data/spec/lutaml/model/with_child_mapping_spec.rb +182 -0
  37. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -11
  38. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +67 -1
  39. data/spec/lutaml/model/xml_adapter_spec.rb +2 -2
  40. data/spec/lutaml/model/xml_mapping_spec.rb +32 -9
  41. data/spec/sample_model_spec.rb +114 -0
  42. metadata +12 -2
data/README.adoc CHANGED
@@ -1977,6 +1977,843 @@ end
1977
1977
  ----
1978
1978
  ====
1979
1979
 
1980
+
1981
+
1982
+ ==== Collection with keyed elements (keyed collection)
1983
+
1984
+ ===== General
1985
+
1986
+ NOTE: This feature is for key-value data model serialization and deserialization
1987
+ only.
1988
+
1989
+ The `map` method with the `root_mappings` option is used for key-value data that
1990
+ is keyed using an attribute value.
1991
+
1992
+ In other words, the key of a key-value pair in a collection is actually the
1993
+ value of an attribute that belongs to the value.
1994
+
1995
+ Simply put, the following two data structures are considered to have the same
1996
+ data:
1997
+
1998
+ [[collection-keyed-by-value]]
1999
+ .A YAML collection as a keyed object, each key with value of the `id` attribute
2000
+ [source,yaml]
2001
+ ----
2002
+ ---
2003
+ vase1:
2004
+ name: Imperial Vase
2005
+ bowl2:
2006
+ name: 18th Century Bowl
2007
+ ----
2008
+
2009
+ [[collection-unkeyed-by-value]]
2010
+ .A YAML collection as an array, the `id` attribute value located inside each element
2011
+ [source,yaml]
2012
+ ----
2013
+ ---
2014
+ - id: vase1
2015
+ name: Imperial Vase
2016
+ - id: bowl2
2017
+ name: 18th Century Bowl
2018
+ ----
2019
+
2020
+ There are key difference between these two data structures:
2021
+
2022
+ * The <<collection-keyed-by-value,keyed object>> (first data structure) ensures
2023
+ uniqueness of the `id` attribute value across the collection, while the
2024
+ <<collection-unkeyed-by-value,array>> (second data structure) does not.
2025
+
2026
+ * The value of the `id` attribute in the first data structure *exists outside*
2027
+ of the formal structure of the data object, instead, it *only exists at the
2028
+ collection level*. On the other hand, the value *exists inside* the structure of
2029
+ the data object in the second data structure.
2030
+
2031
+ The `map` method with the `root_mappings` option, in practice, parses the first
2032
+ data structure in the same way that you would access / manipulate the second
2033
+ data structure, while retaining the serialization semantics of using an
2034
+ attribute as key.
2035
+
2036
+ As a result, usage of lutaml-model across both types of collections are
2037
+ identical (except when serialized).
2038
+
2039
+
2040
+ Syntax:
2041
+
2042
+ [source,ruby]
2043
+ ----
2044
+ class SomeKeyedCollection < Lutaml::Model::Serializable
2045
+ attribute :name_of_attribute, AttributeValueType, collection: true
2046
+
2047
+ json | yaml | toml | key_value do
2048
+ map to: :name_of_attribute, <1>
2049
+ root_mappings: { <2>
2050
+ # `:key` is a reserved keyword
2051
+ value_type_attribute_name_for_key: :key, <3>
2052
+ # `:value` is a reserved keyword (and optional)
2053
+ value_type_attribute_name_for_value: :value, <4>
2054
+ # `[path name]` represents the path to access the value in the
2055
+ # serialization data model to be assigned to
2056
+ # `AttributeValueType.value_type_attribute_name_for_custom_type`
2057
+ value_type_attribute_name_for_custom_type: [path name] <5>
2058
+ }
2059
+ end
2060
+ end
2061
+
2062
+ class AttributeValueType < Lutaml::Model::Serializable
2063
+ attribute :value_type_attribute_name_for_key, :string
2064
+ attribute :value_type_attribute_name_for_value, :string
2065
+ attribute :value_type_attribute_name_for_custom_type, CustomType
2066
+ end
2067
+ ----
2068
+ <1> The `map` option indicates that this class represents the root of the
2069
+ serialization object being passed in. The `name_of_attribute` is the name
2070
+ of the attribute that will hold the collection data. (Mandatory)
2071
+ <2> The `root_mappings` keyword specifies what the collection key represents and
2072
+ and value for model. (Mandatory)
2073
+ <3> The `key` keyword specifies the attribute name of the individual collection
2074
+ object type that represents its key used in the collection. (Mandatory)
2075
+ <4> The `value` keyword specifies the attribute name of the individual collection
2076
+ object type that represents its data used in the collection. (Optional, if
2077
+ not specified, the entire object is used as the value.)
2078
+ <5> The `value_type_attribute_name_for_custom_type` is the name of the attribute
2079
+ inside the individual collection object (`AttributeValueType`) that will hold
2080
+ the value accessible in the serialization data model fetched at `[path name]`.
2081
+
2082
+ The mapping syntax here is similar to that of <<attribute-extraction>> except
2083
+ that the `:key` and `:value` keywords are allowed in addition to `{path}`.
2084
+
2085
+
2086
+ There are 3 cases when working with a keyed collection:
2087
+
2088
+ . Case 1: Only move the "key" into the collection object.
2089
+
2090
+ . Case 2: Move the "key" into the collection object, override all other
2091
+ mappings. Maps `:key` and another attribute, then we override all the other
2092
+ mappings (clean slate)
2093
+
2094
+ . Case 3: Move the "key" into the collection object to an attribute, map the
2095
+ entire "value" to another attribute of the collection object.
2096
+
2097
+
2098
+ ===== Case 1: Only move the "key" into the collection object
2099
+
2100
+ In this case, the "key" of the keyed collection is moved into the collection
2101
+ object, and all other mappings are left as they are.
2102
+
2103
+ When the "key" is moved into the collection object, the following happens:
2104
+
2105
+ * The "key" of the keyed collection maps to a particular attribute of the
2106
+ collection's instance object.
2107
+ * The "value" of the keyed collection (with its various content) maps to the
2108
+ collection's instance object following the collection's instance object type's
2109
+ default mappings.
2110
+
2111
+ The `root_mappings` option **should only contain one mapping**, and the mapping
2112
+ must lead **to the `:key` keyword**.
2113
+
2114
+ Syntax:
2115
+
2116
+ [source,ruby]
2117
+ ----
2118
+ class SomeKeyedCollection < Lutaml::Model::Serializable
2119
+ attribute :name_of_attribute, AttributeValueType, collection: true
2120
+
2121
+ json | yaml | toml | key_value do
2122
+ map to: :name_of_attribute,
2123
+ root_mappings: {
2124
+ value_type_attribute_name_for_key: :key, <1>
2125
+ }
2126
+ end
2127
+ end
2128
+
2129
+ class AttributeValueType < Lutaml::Model::Serializable
2130
+ attribute :value_type_attribute_name_for_key, :string
2131
+ attribute :value_type_attribute_name_for_value, :string
2132
+ attribute :value_type_attribute_name_for_custom_type, CustomType
2133
+ end
2134
+ ----
2135
+ <1> The `:key` keyword specifies that the "key" of the keyed collection maps
2136
+ to the `value_type_attribute_name_for_key` attribute of the collection's
2137
+ instance object (i.e. `AttributeValueType`).
2138
+
2139
+
2140
+
2141
+ .Using `map` with `root_mappings` (only `key`) to map a keyed collection into individual models
2142
+ [example]
2143
+ ====
2144
+ Given this data:
2145
+
2146
+ [source,yaml]
2147
+ ----
2148
+ ---
2149
+ vase1:
2150
+ name: Imperial Vase
2151
+ bowl2:
2152
+ name: 18th Century Bowl
2153
+ ----
2154
+
2155
+ A model can be defined for this YAML as follows:
2156
+
2157
+ [source,ruby]
2158
+ ----
2159
+ # This is a normal Lutaml::Model class
2160
+ class Ceramic < Lutaml::Model::Serializable
2161
+ attribute :ceramic_id, :string
2162
+ attribute :ceramic_name, :string
2163
+
2164
+ key_value do
2165
+ map 'id', to: :ceramic_id
2166
+ map 'name', to: :ceramic_name
2167
+ end
2168
+ end
2169
+
2170
+ # This is Lutaml::Model class that represents the collection of Ceramic objects
2171
+ class CeramicCollection < Lutaml::Model::Serializable
2172
+ attribute :ceramics, Ceramic, collection: true
2173
+
2174
+ key_value do
2175
+ map to: :ceramics, # All data goes to the `ceramics` attribute
2176
+ root_mappings: {
2177
+ # The key of an object in this collection is mapped to the ceramic_id
2178
+ # attribute of the Ceramic object.
2179
+ ceramic_id: :key # "key" is a reserved keyword
2180
+ }
2181
+ end
2182
+ end
2183
+ ----
2184
+
2185
+ [source,ruby]
2186
+ ----
2187
+ # Parsing the YAML collection with dynamic data keys
2188
+ > ceramic_collection = CeramicCollection.from_yaml(yaml)
2189
+ > #<CeramicCollection:0x0000000104ac7240
2190
+ @ceramics=
2191
+ [#<Ceramic:0x0000000104ac6e30 @ceramic_id="vase1", @ceramic_name="Imperial Vase">,
2192
+ #<Ceramic:0x0000000104ac58f0 @ceramic_id="bowl2", @ceramic_name="18th Century Bowl">]
2193
+
2194
+ # NOTE: When an individual Ceramic object is serialized, the `id` attribute is
2195
+ # the original key in the incoming YAML data, and because there were no mappings defined along with the `:key`, everyting is mapped to the `Ceramic` object using the mappings defined in the `Ceramic` class.
2196
+ > first_ceramic = ceramic_collection.ceramics.first
2197
+ > puts first_ceramic.to_yaml
2198
+ =>
2199
+ # ---
2200
+ # id: vase1
2201
+ # name: Imperial Vase
2202
+
2203
+ # NOTE: When in a collection, the `ceramic_id` attribute is used to key the data,
2204
+ # and it disappears from the individual object.
2205
+ > puts ceramic_collection.to_yaml
2206
+ =>
2207
+ # ---
2208
+ # vase1:
2209
+ # name: Imperial Vase
2210
+ # bowl2:
2211
+ # name: 18th Century Bowl
2212
+
2213
+ # NOTE: When the collection is serialized, the `ceramic_id` attribute is used to
2214
+ # key the data. This is defined through the `map` with `root_mappings` method in
2215
+ # CeramicCollection.
2216
+ > new_collection = CeramicCollection.new(ceramics: [
2217
+ Ceramic.new(ceramic_id: "vase1", ceramic_name: "Imperial Vase"),
2218
+ Ceramic.new(ceramic_id: "bowl2", ceramic_name: "18th Century Bowl")
2219
+ ])
2220
+ > puts new_collection.to_yaml
2221
+ =>
2222
+ # ---
2223
+ # vase1:
2224
+ # name: Imperial Vase
2225
+ # bowl2:
2226
+ # name: 18th Century Bowl
2227
+ ----
2228
+ ====
2229
+
2230
+
2231
+
2232
+ ===== Case 2: Mapping the `key` and complex ``value``s
2233
+
2234
+ In this use case, the "key" of the keyed collection is moved into the collection
2235
+ object, and all other mappings are overridden.
2236
+
2237
+ When more than one mapping rule exists in the `root_mappings` option, the
2238
+ `root_mappings` option will override all other mappings in the collection object.
2239
+
2240
+ When the "key" is moved into the collection object, the following happens:
2241
+
2242
+ * The "key" of the keyed collection maps to a particular attribute of the
2243
+ collection's instance object.
2244
+
2245
+ * The data of the "value" of the keyed collection have their own mappings
2246
+ overridden by the new mapping rules of the `root_mappings` option.
2247
+
2248
+ The `root_mappings` option **can contain more than one mapping**, with one of
2249
+ the mapping rules leading **to the `:key` keyword**.
2250
+
2251
+
2252
+ Syntax:
2253
+
2254
+ [source,ruby]
2255
+ ----
2256
+ class SomeKeyedCollection < Lutaml::Model::Serializable
2257
+ attribute :name_of_attribute, AttributeValueType, collection: true
2258
+
2259
+ json | yaml | toml | key_value do
2260
+ map to: :name_of_attribute,
2261
+ root_mappings: {
2262
+ value_type_attribute_name_for_key: :key, <1>
2263
+ value_type_attribute_name_for_value_data_1: "serialization_format_name_1", <2>
2264
+ value_type_attribute_name_for_value_data_2: "serialization_format_name_2",
2265
+ value_type_attribute_name_for_value_data_3: ["path name", ...] <3>
2266
+ # ...
2267
+ }
2268
+ end
2269
+ end
2270
+
2271
+ class AttributeValueType < Lutaml::Model::Serializable
2272
+ attribute :value_type_attribute_name_for_key, :string
2273
+ attribute :value_type_attribute_name_for_value_data_1, :string
2274
+ attribute :value_type_attribute_name_for_value_data_2, SomeType
2275
+ attribute :value_type_attribute_name_for_value_data_3, MoreType
2276
+ # ...
2277
+ end
2278
+ ----
2279
+ <1> The `:key` keyword specifies that the "key" of the keyed collection maps
2280
+ to the `value_type_attribute_name_for_key` attribute of the collection's
2281
+ instance object (i.e. `AttributeValueType`).
2282
+ <2> The `serialization_format_name_1` target specifies that the
2283
+ `serialization_format_name_2` key of the keyed collection value maps to the
2284
+ `value_type_attribute_name_for_value_data_1` attribute of the collection's
2285
+ instance object.
2286
+ <3> The `[path name]` target specifies to fetch from `[path name]` in the
2287
+ serialization data model to be assigned to the
2288
+ `value_type_attribute_name_for_value_data_3` attribute of the collection's
2289
+ instance object.
2290
+
2291
+ When the `root_mappings` mapping contains more than one mapping rule that is not
2292
+ to `:key` or `:value`, the `root_mappings` mapping will override all other
2293
+ mappings in the collection object. This means that unmapped attributes in
2294
+ `root_mappings` will not be incorporated in the collection instance objects.
2295
+
2296
+ .Using `map` with `root_mappings` (`key` and complex `value`) to map a keyed collection into individual models
2297
+ [example]
2298
+ ====
2299
+
2300
+ [source,yaml]
2301
+ ----
2302
+ "vase1":
2303
+ type: "vase"
2304
+ details:
2305
+ name: "Imperial Vase"
2306
+ insignia: "Tang Tianbao"
2307
+ urn:
2308
+ primary: "urn:ceramic:vase:vase1"
2309
+ "bowl2":
2310
+ type: "bowl"
2311
+ details:
2312
+ name: "18th Century Bowl"
2313
+ insignia: "Ming Wanli"
2314
+ urn:
2315
+ primary: "urn:ceramic:bowl:bowl2"
2316
+ ----
2317
+
2318
+ A model can be defined for this YAML as follows:
2319
+
2320
+ [source,ruby]
2321
+ ----
2322
+ # This is a normal Lutaml::Model class
2323
+ class CeramicDetails < Lutaml::Model::Serializable
2324
+ attribute :name, :string
2325
+ attribute :insignia, :string
2326
+
2327
+ key_value do
2328
+ map 'name', to: :name
2329
+ map 'insignia', to: :insignia
2330
+ end
2331
+ end
2332
+
2333
+ # This is a normal Lutaml::Model class
2334
+ class Ceramic < Lutaml::Model::Serializable
2335
+ attribute :ceramic_id, :string
2336
+ attribute :ceramic_type, :string
2337
+ attribute :ceramic_details, CeramicDetails
2338
+ attribute :ceramic_urn, :string
2339
+
2340
+ key_value do
2341
+ map 'id', to: :ceramic_id
2342
+ map 'type', to: :ceramic_type
2343
+ map 'details', to: :ceramic_details
2344
+ map 'urn', to: :ceramic_urn
2345
+ end
2346
+ end
2347
+
2348
+ # This is Lutaml::Model class that represents the collection of Ceramic objects
2349
+ class CeramicCollection < Lutaml::Model::Serializable
2350
+ attribute :ceramics, Ceramic, collection: true
2351
+
2352
+ key_value do
2353
+ map to: :ceramics, # All data goes to the `ceramics` attribute
2354
+ root_mappings: {
2355
+ # The key of an object in this collection is mapped to the ceramic_id
2356
+ # attribute of the Ceramic object.
2357
+ # (e.g. `vase1`, `bowl2`)
2358
+ ceramic_id: :key,
2359
+ ceramic_type: :type,
2360
+ ceramic_details: "details",
2361
+ ceramic_urn: ["urn", "primary"]
2362
+ }
2363
+ end
2364
+ end
2365
+ ----
2366
+
2367
+ The output becomes:
2368
+
2369
+ [source,ruby]
2370
+ ----
2371
+ > ceramics_collection = CeramicCollection.from_yaml(yaml)
2372
+ => #<CeramicCollection:0x0000000107a2cf30
2373
+ @ceramics=
2374
+ [#<Ceramic:0x0000000107a2cf30
2375
+ @ceramic_id="vase1",
2376
+ @ceramic_type="vase",
2377
+ @ceramic_details=
2378
+ #<CeramicDetails:0x0000000107a2cf30
2379
+ @name="Imperial Vase",
2380
+ @insignia="Tang Tianbao">,
2381
+ @ceramic_urn="urn:ceramic:vase:vase1">,
2382
+ #<Ceramic:0x0000000107a2cf30
2383
+ @ceramic_id="bowl2",
2384
+ @ceramic_type="bowl",
2385
+ @ceramic_details=
2386
+ #<CeramicDetails:0x0000000107a2cf30
2387
+ @name="18th Century Bowl",
2388
+ @insignia="Ming Wanli">
2389
+ @ceramic_urn="urn:ceramic:bowl:bowl2">]
2390
+
2391
+ > first_ceramic = ceramics_collection.ceramics.first
2392
+ > puts first_ceramic.to_yaml
2393
+ =>
2394
+ # ---
2395
+ # id: vase1
2396
+ # type: vase
2397
+ # details:
2398
+ # name: Imperial Vase
2399
+ # insignia: Tang Tianbao
2400
+ # urn: urn:ceramic:vase:vase1
2401
+
2402
+ > new_collection = CeramicCollection.new(ceramics: [
2403
+ Ceramic.new(ceramic_id: "vase1",
2404
+ ceramic_type: "vase",
2405
+ ceramic_urn: "urn:ceramic:vase:vase1",
2406
+ ceramic_details: CeramicDetails.new(
2407
+ name: "Imperial Vase", insignia: "Tang Tianbao")
2408
+ ),
2409
+ Ceramic.new(ceramic_id: "bowl2",
2410
+ ceramic_type: "bowl",
2411
+ ceramic_urn: "urn:ceramic:vase:bowl2",
2412
+ ceramic_details: CeramicDetails.new(
2413
+ name: "18th Century Bowl", insignia: "Ming Wanli")
2414
+ )
2415
+ ])
2416
+ > new_collection.to_yaml
2417
+ >
2418
+ # ---
2419
+ # vase1:
2420
+ # type: vase
2421
+ # details:
2422
+ # name: Imperial Vase
2423
+ # insignia: Tang Tianbao
2424
+ # urn:
2425
+ # primary: urn:ceramic:vase:vase1
2426
+ # bowl2:
2427
+ # type: bowl
2428
+ # details:
2429
+ # name: 18th Century Bowl
2430
+ # insignia: Ming Wanli
2431
+ # urn:
2432
+ # primary: urn:ceramic:bowl:bowl2
2433
+ ----
2434
+ ====
2435
+
2436
+
2437
+ ===== Case 3: Mapping the `key` and delegating `value` to an inner object
2438
+
2439
+ In this use case, the "key" of the keyed collection is moved into the collection
2440
+ object to an attribute, and the entire "value" of the keyed collection is mapped
2441
+ to another attribute of the collection object.
2442
+
2443
+ When the "key" is moved into the collection object, the following happens:
2444
+
2445
+ * The "key" of the keyed collection maps to a particular attribute of the
2446
+ collection's instance object.
2447
+
2448
+ * The data of the "value" of the keyed collection will be entirely mapped into
2449
+ an attribute of the collection's instance object.
2450
+
2451
+ * The original mapping of the "value" attribute of the collection's instance
2452
+ object is retained.
2453
+
2454
+ The `root_mappings` option **should only contain two mappings**, and the mappings
2455
+ must lead **to both the `:key` and `:value` keywords**.
2456
+
2457
+
2458
+ Syntax:
2459
+
2460
+ [source,ruby]
2461
+ ----
2462
+ class SomeKeyedCollection < Lutaml::Model::Serializable
2463
+ attribute :name_of_attribute, AttributeValueType, collection: true
2464
+
2465
+ json | yaml | toml | key_value do
2466
+ map to: :name_of_attribute,
2467
+ root_mappings: {
2468
+ value_type_attribute_name_for_key: :key, <1>
2469
+ value_type_attribute_name_for_value: :value <2>
2470
+ }
2471
+ end
2472
+ end
2473
+
2474
+ class AttributeValueType < Lutaml::Model::Serializable
2475
+ attribute :value_type_attribute_name_for_key, :string
2476
+ attribute :value_type_attribute_name_for_value, SomeObject
2477
+ end
2478
+ ----
2479
+ <1> The `:key` keyword specifies that the "key" of the keyed collection maps
2480
+ to the `value_type_attribute_name_for_key` attribute of the collection's
2481
+ instance object (i.e. `AttributeValueType`).
2482
+ <2> The `:value` keyword specifies that the entire "value" of the keyed
2483
+ collection maps to the `value_type_attribute_name_for_value` attribute of the
2484
+ collection's instance object (i.e. `SomeObject`).
2485
+
2486
+ When the `root_mappings` mapping contains more than one mapping rule, the
2487
+ `root_mappings` mapping will override all other mappings in the collection
2488
+ object. This means that unmapped attributes in `root_mappings` will not be
2489
+ incorporated in the collection instance objects.
2490
+
2491
+
2492
+
2493
+ .Using `map` with `root_mappings` (`key` and `value`) to map a keyed collection into individual models
2494
+ [example]
2495
+ ====
2496
+ Given this data:
2497
+
2498
+ [source,yaml]
2499
+ ----
2500
+ ---
2501
+ vase1:
2502
+ name: Imperial Vase
2503
+ insignia: "Tang Tianbao"
2504
+ bowl2:
2505
+ name: 18th Century Bowl
2506
+ insignia: "Ming Wanli"
2507
+ ----
2508
+
2509
+ A model can be defined for this YAML as follows:
2510
+
2511
+ [source,ruby]
2512
+ ----
2513
+ # This is a normal Lutaml::Model class
2514
+ class CeramicDetails < Lutaml::Model::Serializable
2515
+ attribute :name, :string
2516
+ attribute :insignia, :string
2517
+
2518
+ key_value do
2519
+ map 'name', to: :name
2520
+ map 'insignia', to: :insignia
2521
+ end
2522
+ end
2523
+
2524
+ # This is a normal Lutaml::Model class
2525
+ class Ceramic < Lutaml::Model::Serializable
2526
+ attribute :ceramic_id, :string
2527
+ attribute :ceramic_details, CeramicDetails
2528
+
2529
+ key_value do
2530
+ map 'id', to: :ceramic_id
2531
+ map 'details', to: :ceramic_details
2532
+ end
2533
+ end
2534
+
2535
+ # This is Lutaml::Model class that represents the collection of Ceramic objects
2536
+ class CeramicCollection < Lutaml::Model::Serializable
2537
+ attribute :ceramics, Ceramic, collection: true
2538
+
2539
+ key_value do
2540
+ map to: :ceramics, # All data goes to the `ceramics` attribute
2541
+ root_mappings: {
2542
+ # The key of an object in this collection is mapped to the ceramic_id
2543
+ # attribute of the Ceramic object.
2544
+ # (e.g. `vase1`, `bowl2`)
2545
+ ceramic_id: :key,
2546
+ # The value of an object in this collection is mapped to the
2547
+ # ceramic_details attribute of the Ceramic object.
2548
+ # (e.g. `name: 18th Century Bowl`, `insignia: "Ming Wanli"`
2549
+ ceramic_details: :value
2550
+ }
2551
+ end
2552
+ end
2553
+ ----
2554
+
2555
+ [source,ruby]
2556
+ ----
2557
+ # Parsing the YAML collection with dynamic data keys
2558
+ > ceramic_collection = CeramicCollection.from_yaml(yaml)
2559
+ > #<CeramicCollection:0x0000000104ac7240
2560
+ @ceramics=
2561
+ [#<Ceramic:0x0000000104ac6e30
2562
+ @ceramic_id="vase1",
2563
+ @ceramic_details=
2564
+ #<CeramicDetails:0x0000000104ac6e30
2565
+ @name="Imperial Vase",
2566
+ @insignia="Tang Tianbao">,
2567
+ #<Ceramic:0x0000000104ac58f0
2568
+ @ceramic_id="bowl2",
2569
+ @ceramic_details=
2570
+ #<CeramicDetails:0x0000000104ac58f0
2571
+ @name="18th Century Bowl",
2572
+ @insignia="Ming Wanli">]
2573
+
2574
+ # NOTE: When an individual Ceramic object is serialized, the `id` attribute is
2575
+ # the original key in the incoming YAML data.
2576
+ > first_ceramic = ceramic_collection.ceramics.first
2577
+ > puts first_ceramic.to_yaml
2578
+ =>
2579
+ # ---
2580
+ # id: vase1
2581
+ # details:
2582
+ # name: Imperial Vase
2583
+ # insignia: Tang Tianbao
2584
+
2585
+ # NOTE: When in a collection, the `ceramic_id` attribute is used to key the data,
2586
+ # and it disappears from the individual object.
2587
+ > puts ceramic_collection.to_yaml
2588
+ =>
2589
+ # ---
2590
+ # vase1:
2591
+ # name: Imperial Vase
2592
+ # insignia: Tang Tianbao
2593
+ # bowl2:
2594
+ # name: 18th Century Bowl
2595
+ # insignia: Ming Wanli
2596
+
2597
+ # NOTE: When the collection is serialized, the `ceramic_id` attribute is used to
2598
+ # key the data. This is defined through the `map` with `root_mappings` method in
2599
+ # CeramicCollection.
2600
+ > new_collection = CeramicCollection.new(ceramics: [
2601
+ Ceramic.new(ceramic_id: "vase1",
2602
+ ceramic_details: CeramicDetails.new(
2603
+ name: "Imperial Vase", insignia: "Tang Tianbao")
2604
+ ),
2605
+ Ceramic.new(ceramic_id: "bowl2",
2606
+ ceramic_details: CeramicDetails.new(
2607
+ name: "18th Century Bowl", insignia: "Ming Wanli")
2608
+ )
2609
+ ])
2610
+ > puts new_collection.to_yaml
2611
+ =>
2612
+ # ---
2613
+ # vase1:
2614
+ # name: Imperial Vase
2615
+ # insignia: Tang Tianbao
2616
+ # bowl2:
2617
+ # name: 18th Century Bowl
2618
+ # insignia: Ming Wanli
2619
+ ----
2620
+ ====
2621
+
2622
+
2623
+ [[attribute-extraction]]
2624
+ ==== Attribute extraction
2625
+
2626
+ NOTE: This feature is for key-value data model serialization only.
2627
+
2628
+ The `child_mappings` option is used to extract results from a key-value
2629
+ serialization data model (JSON, YAML, TOML) into a `Lutaml::Model::Serializable`
2630
+ object (collection or not).
2631
+
2632
+ The values are extracted from the key-value data model using the list of keys
2633
+ provided.
2634
+
2635
+ Syntax:
2636
+
2637
+ [source,ruby]
2638
+ ----
2639
+ class SomeObject < Lutaml::Model::Serializable
2640
+ attribute :name_of_attribute, AttributeValueType, collection: true
2641
+
2642
+ json | yaml | toml | key_value do
2643
+ map 'key_value_model_attribute_name', to: :name_of_attribute,
2644
+ child_mappings: {
2645
+ value_type_attribute_name_1: <1>
2646
+ {path_to_value_1}, <2>
2647
+ value_type_attribute_name_2:
2648
+ {path_to_value_2},
2649
+ # ...
2650
+ }
2651
+ end
2652
+ end
2653
+ ----
2654
+ <1> The `value_type_attribute_name_1` is the attribute name in the
2655
+ `AttributeValueType` model. The value of this attribute will be assigned the key
2656
+ of the hash in the key-value data model.
2657
+
2658
+ <2> The `path_to_value_1` is an array of keys that represent the path to the
2659
+ value in the key-value serialization data model. The keys are used to extract the value from
2660
+ the key-value serialization data model and assign it to the attribute in the
2661
+ `AttributeValueType` model.
2662
+ +
2663
+ The `path_to_value` is in a nested array format with each value a symbol or a
2664
+ string, where each symbol represents a key to traverse down. The last key in the
2665
+ path is the value to be extracted.
2666
+
2667
+ .Determining the path to value in a key-value data model
2668
+ [example]
2669
+ ====
2670
+ The following JSON contains 2 keys in schema named `engine` and `gearbox`.
2671
+
2672
+ [source,json]
2673
+ ----
2674
+ {
2675
+ "components": {
2676
+ "engine": {
2677
+ "manufacturer": "Ford",
2678
+ "model": "V8"
2679
+ },
2680
+ "gearbox": {
2681
+ "manufacturer": "Toyota",
2682
+ "model": "4-speed"
2683
+ }
2684
+ }
2685
+ }
2686
+ ----
2687
+
2688
+ The path to value for the `engine` schema is `[:components, :engine]` and for
2689
+ the `gearbox` schema is `[:components, :gearbox]`.
2690
+ ====
2691
+
2692
+ In `path_to_value`, the `:key` and `:value` are reserved instructions used to
2693
+ assign the key or value of the serialization data respectively as the value to
2694
+ the attribute.
2695
+
2696
+ [example]
2697
+ ====
2698
+ In the following JSON content, the `path_to_value` for the object keys named
2699
+ `engine` and `gearbox` will utilize the `:key` keyword to assign the key of the
2700
+ object as the value of a designated attribute.
2701
+
2702
+ [source,json]
2703
+ ----
2704
+ {
2705
+ "components": {
2706
+ "engine": { /*...*/ },
2707
+ "gearbox": { /*...*/ }
2708
+ }
2709
+ }
2710
+ ----
2711
+ ====
2712
+
2713
+ If a specified value path is not found, the corresponding attribute in the model
2714
+ will be assigned a `nil` value.
2715
+
2716
+ .Attribute values set to `nil` when the `path_to_value` is not found
2717
+ [example]
2718
+ ====
2719
+ In the following JSON content, the `path_to_value` of `[:extras, :sunroof]` and
2720
+ `[:extras, :drinks_cooler]` at the object `"gearbox"` would be set to `nil`.
2721
+
2722
+ [source,json]
2723
+ ----
2724
+ {
2725
+ "components": {
2726
+ "engine": {
2727
+ "manufacturer": "Ford",
2728
+ "extras": {
2729
+ "sunroof": true,
2730
+ "drinks_cooler": true
2731
+ }
2732
+ },
2733
+ "gearbox": {
2734
+ "manufacturer": "Toyota"
2735
+ }
2736
+ }
2737
+ }
2738
+ ----
2739
+ ====
2740
+
2741
+
2742
+ .Using the `child_mappings` option to extract values from a key-value data model
2743
+ [example]
2744
+ ====
2745
+ The following JSON contains 2 keys in schema named `foo` and `bar`.
2746
+
2747
+ [source,json]
2748
+ ----
2749
+ {
2750
+ "schemas": {
2751
+ "foo": { <1>
2752
+ "path": { <2>
2753
+ "link": "link one",
2754
+ "name": "one"
2755
+ }
2756
+ },
2757
+ "bar": { <1>
2758
+ "path": { <2>
2759
+ "link": "link two",
2760
+ "name": "two"
2761
+ }
2762
+ }
2763
+ }
2764
+ }
2765
+ ----
2766
+ <1> The keys `foo` and `bar` are to be mapped to the `id` attribute.
2767
+ <2> The nested `path.link` and `path.name` keys are used as the `link` and
2768
+ `name` attributes, respectively.
2769
+
2770
+ A model can be defined for this JSON as follows:
2771
+
2772
+ [source,ruby]
2773
+ ----
2774
+ class Schema < Lutaml::Model::Serializable
2775
+ attribute :id, :string
2776
+ attribute :link, :string
2777
+ attribute :name, :string
2778
+ end
2779
+
2780
+ class ChildMappingClass < Lutaml::Model::Serializable
2781
+ attribute :schemas, Schema, collection: true
2782
+
2783
+ json do
2784
+ map "schemas", to: :schemas,
2785
+ child_mappings: {
2786
+ id: :key,
2787
+ link: %i[path link],
2788
+ name: %i[path name],
2789
+ }
2790
+ end
2791
+ end
2792
+ ----
2793
+
2794
+ The output becomes:
2795
+
2796
+ [source,ruby]
2797
+ ----
2798
+ > ChildMappingClass.from_json(json)
2799
+ > #<ChildMappingClass:0x0000000104ac7240
2800
+ @schemas=
2801
+ [#<Schema:0x0000000104ac6e30 @id="foo", @link="link one", @name="one">,
2802
+ #<Schema:0x0000000104ac58f0 @id="bar", @link="link two", @name="two">]>
2803
+ > ChildMappingClass.new(schemas: [Schema.new(id: "foo", link: "link one", name: "one"), Schema.new(id: "bar", link: "link two", name: "two")]).to_json
2804
+ > #{"schemas"=>{"foo"=>{"path"=>{"link"=>"link one", "name"=>"one"}}, {"bar"=>{"path"=>{"link"=>"link two", "name"=>"two"}}}}}
2805
+ ----
2806
+
2807
+ In this example:
2808
+
2809
+ * The `key` of each schema (`foo` and `bar`) is mapped to the `id` attribute.
2810
+
2811
+ * The nested `path.link` and `path.name` keys are mapped to the `link` and
2812
+ `name` attributes, respectively.
2813
+ ====
2814
+
2815
+
2816
+
1980
2817
  [[separate-serialization-model]]
1981
2818
  === Separate serialization model
1982
2819
 
@@ -2131,6 +2968,121 @@ end
2131
2968
 
2132
2969
  === Advanced attribute mapping
2133
2970
 
2971
+ ==== Multiple mappings to single attribute
2972
+
2973
+ The mapping methods support multiple names mapping to a single attribute using
2974
+ an array of names.
2975
+
2976
+ Syntax:
2977
+
2978
+ [source,ruby]
2979
+ ----
2980
+ json | yaml | toml | key_value do
2981
+ map ["name1", "name2"], to: :attribute_name
2982
+ end
2983
+
2984
+ xml do
2985
+ map_element ["name1", "name2"], to: :attribute_name
2986
+ map_attribute ["attr1", "attr2"], to: :attribute_name
2987
+ end
2988
+ ----
2989
+
2990
+ When serializing, the first element in the array of mapped names is always used
2991
+ as the output name.
2992
+
2993
+
2994
+ .Using multiple names to map to a single attribute
2995
+ [example]
2996
+ ====
2997
+ [source,ruby]
2998
+ ----
2999
+ class CustomModel < Lutaml::Model::Serializable
3000
+ attribute :full_name, Lutaml::Model::Type::String
3001
+ attribute :color, Lutaml::Model::Type::String
3002
+ attribute :id, Lutaml::Model::Type::String
3003
+
3004
+ json do
3005
+ map ["name", "custom_name"], with: { to: :name_to_json, from: :name_from_json }
3006
+ map ["color", "shade"], with: { to: :color_to_json, from: :color_from_json }
3007
+ end
3008
+
3009
+ xml do
3010
+ root "CustomModel"
3011
+ map_element ["name", "custom-name"], with: { to: :name_to_xml, from: :name_from_xml }
3012
+ map_element ["color", "shade"], with: { to: :color_to_xml, from: :color_from_xml }
3013
+ map_attribute ["id", "identifier"], to: :id
3014
+ end
3015
+
3016
+ # Custom methods for JSON
3017
+ def name_to_json(model, doc)
3018
+ doc["name"] = "JSON Model: #{model.full_name}"
3019
+ end
3020
+
3021
+ def name_from_json(model, value)
3022
+ model.full_name = value&.sub(/^JSON Model: /, "")
3023
+ end
3024
+
3025
+ def color_to_json(model, doc)
3026
+ doc["color"] = model.color.upcase
3027
+ end
3028
+
3029
+ def color_from_json(model, value)
3030
+ model.color = value&.downcase
3031
+ end
3032
+
3033
+ # Custom methods for XML
3034
+ def name_to_xml(model, parent, doc)
3035
+ el = doc.create_element("name")
3036
+ doc.add_text(el, "XML Model: #{model.full_name}")
3037
+ doc.add_element(parent, el)
3038
+ end
3039
+
3040
+ def name_from_xml(model, value)
3041
+ model.full_name = value.sub(/^XML Model: /, "")
3042
+ end
3043
+
3044
+ def color_to_xml(model, parent, doc)
3045
+ el = doc.create_element("color")
3046
+ doc.add_text(el, model.color.upcase)
3047
+ doc.add_element(parent, el)
3048
+ end
3049
+
3050
+ def color_from_xml(model, value)
3051
+ model.color = value.downcase
3052
+ end
3053
+ end
3054
+ ----
3055
+
3056
+ For JSON:
3057
+ [source,json]
3058
+ ----
3059
+ {
3060
+ "custom_name": "JSON Model: Vase",
3061
+ "shade": "BLUE",
3062
+ "identifier": "123"
3063
+ }
3064
+ ----
3065
+
3066
+ For XML:
3067
+ [source,xml]
3068
+ ----
3069
+ <CustomModel id="123">
3070
+ <name>XML Model: Vase</name>
3071
+ <color>BLUE</color>
3072
+ </CustomModel>
3073
+ ----
3074
+
3075
+ [source,ruby]
3076
+ ----
3077
+ > model = CustomModel.from_json(json)
3078
+ > model.full_name
3079
+ > # "Vase"
3080
+ > model.color
3081
+ > # "blue"
3082
+ ----
3083
+ ====
3084
+
3085
+
2134
3086
  ==== Attribute mapping delegation
2135
3087
 
2136
3088
  Delegate attribute mappings to nested objects using the `delegate` option.
@@ -2353,190 +3305,6 @@ end
2353
3305
  ====
2354
3306
 
2355
3307
 
2356
- [[attribute-extraction]]
2357
- ==== Attribute extraction (for key-value data models only)
2358
-
2359
- NOTE: This feature is for key-value data model serialization only.
2360
-
2361
- The `child_mappings` option is used to extract results from a key-value data
2362
- model (JSON, YAML, TOML) into a `Lutaml::Model` collection.
2363
-
2364
- The values are extracted from the key-value data model using the list of keys
2365
- provided.
2366
-
2367
- Syntax:
2368
-
2369
- [source,ruby]
2370
- ----
2371
- json | yaml | toml do
2372
- map 'key_value_model_attribute_name', to: :name_of_attribute,
2373
- child_mappings: {
2374
- key_attribute_name_1: <1>
2375
- {path_to_value_1}, <2>
2376
- key_attribute_name_2:
2377
- {path_to_value_2},
2378
- # ...
2379
- }
2380
- end
2381
- ----
2382
- <1> The `key_attribute_name_1` is the attribute name in the model. The value of
2383
- this attribute will be assigned the key of the hash in the key-value data model.
2384
-
2385
- <2> The `path_to_value_1` is an array of keys that represent the path to the
2386
- value in the key-value data model. The keys are used to extract the value from
2387
- the key-value data model and assign it to the attribute in the model.
2388
-
2389
- The `path_to_value` is in a nested array format with each value a symbol, where
2390
- each symbol represents a key to traverse down. The last key in the path is the
2391
- value to be extracted.
2392
-
2393
- .Determining the path to value in a key-value data model
2394
- [example]
2395
- ====
2396
- The following JSON contains 2 keys in schema named `engine` and `gearbox`.
2397
-
2398
- [source,json]
2399
- ----
2400
- {
2401
- "components": {
2402
- "engine": {
2403
- "manufacturer": "Ford",
2404
- "model": "V8"
2405
- },
2406
- "gearbox": {
2407
- "manufacturer": "Toyota",
2408
- "model": "4-speed"
2409
- }
2410
- }
2411
- }
2412
- ----
2413
-
2414
- The path to value for the `engine` schema is `[:components, :engine]` and for
2415
- the `gearbox` schema is `[:components, :gearbox]`.
2416
- ====
2417
-
2418
- In `path_to_value`, the `:key` and `:value` are reserved instructions used to
2419
- assign the key or value of the serialization data respectively as the value to
2420
- the attribute.
2421
-
2422
- [example]
2423
- ====
2424
- In the following JSON content, the `path_to_value` for the object keys named
2425
- `engine` and `gearbox` will utilize the `:key` keyword to assign the key of the
2426
- object as the value of a designated attribute.
2427
-
2428
- [source,json]
2429
- ----
2430
- {
2431
- "components": {
2432
- "engine": { /*...*/ },
2433
- "gearbox": { /*...*/ }
2434
- }
2435
- }
2436
- ----
2437
- ====
2438
-
2439
- If a specified value path is not found, the corresponding attribute in the model
2440
- will be assigned a `nil` value.
2441
-
2442
- .Attribute values set to `nil` when the `path_to_value` is not found
2443
- [example]
2444
- ====
2445
- In the following JSON content, the `path_to_value` of `[:extras, :sunroof]` and
2446
- `[:extras, :drinks_cooler]` at the object `"gearbox"` would be set to `nil`.
2447
-
2448
- [source,json]
2449
- ----
2450
- {
2451
- "components": {
2452
- "engine": {
2453
- "manufacturer": "Ford",
2454
- "extras": {
2455
- "sunroof": true,
2456
- "drinks_cooler": true
2457
- }
2458
- },
2459
- "gearbox": {
2460
- "manufacturer": "Toyota"
2461
- }
2462
- }
2463
- }
2464
- ----
2465
- ====
2466
-
2467
-
2468
- .Using the `child_mappings` option to extract values from a key-value data model
2469
- [example]
2470
- ====
2471
- The following JSON contains 2 keys in schema named `foo` and `bar`.
2472
-
2473
- [source,json]
2474
- ----
2475
- {
2476
- "schemas": {
2477
- "foo": { <1>
2478
- "path": { <2>
2479
- "link": "link one",
2480
- "name": "one"
2481
- }
2482
- },
2483
- "bar": { <1>
2484
- "path": { <2>
2485
- "link": "link two",
2486
- "name": "two"
2487
- }
2488
- }
2489
- }
2490
- }
2491
- ----
2492
- <1> The keys `foo` and `bar` are to be mapped to the `id` attribute.
2493
- <2> The nested `path.link` and `path.name` keys are used as the `link` and
2494
- `name` attributes, respectively.
2495
-
2496
- A model can be defined for this JSON as follows:
2497
-
2498
- [source,ruby]
2499
- ----
2500
- class Schema < Lutaml::Model::Serializable
2501
- attribute :id, :string
2502
- attribute :link, :string
2503
- attribute :name, :string
2504
- end
2505
-
2506
- class ChildMappingClass < Lutaml::Model::Serializable
2507
- attribute :schemas, Schema, collection: true
2508
-
2509
- json do
2510
- map "schemas", to: :schemas,
2511
- child_mappings: {
2512
- id: :key,
2513
- link: %i[path link],
2514
- name: %i[path name],
2515
- }
2516
- end
2517
- end
2518
- ----
2519
-
2520
- The output becomes:
2521
-
2522
- [source,ruby]
2523
- ----
2524
- > ChildMappingClass.from_json(json)
2525
- > #<ChildMappingClass:0x0000000104ac7240
2526
- @schemas=
2527
- [#<Schema:0x0000000104ac6e30 @id="foo", @link="link one", @name="one">,
2528
- #<Schema:0x0000000104ac58f0 @id="bar", @link="link two", @name="two">]>
2529
- > ChildMappingClass.new(schemas: [Schema.new(id: "foo", link: "link one", name: "one"), Schema.new(id: "bar", link: "link two", name: "two")]).to_json
2530
- > #{"schemas"=>{"foo"=>{"path"=>{"link"=>"link one", "name"=>"one"}}, {"bar"=>{"path"=>{"link"=>"link two", "name"=>"two"}}}}
2531
- ----
2532
-
2533
- In this example:
2534
-
2535
- * The `key` of each schema (`foo` and `bar`) is mapped to the `id` attribute.
2536
-
2537
- * The nested `path.link` and `path.name` keys are mapped to the `link` and
2538
- `name` attributes, respectively.
2539
- ====
2540
3308
 
2541
3309
 
2542
3310
  == Validation
@@ -2641,6 +3409,13 @@ klin.validate
2641
3409
  Lutaml::Model uses an adapter pattern to support multiple libraries for each
2642
3410
  serialization format.
2643
3411
 
3412
+ Lutaml::Model supports the following serialization formats:
3413
+
3414
+ * XML (https://www.w3.org/TR/xmlschema-1/[W3C XML Schema (Second Edition)], XML 1.0)
3415
+ * YAML (https://yaml.org/[YAML version 1.2])
3416
+ * JSON (https://www.ecma-international.org/publications-and-standards/standards/ecma-404/[ECMA-404 The JSON Data Interchange Standard], unofficial link: https://www.json.org[JSON])
3417
+ * TOML (https://toml.io/en[TOML version 1.0])
3418
+
2644
3419
  You will need to specify the configuration for the adapter you want to use. The
2645
3420
  easiest way is to copy and paste the following configuration into your code.
2646
3421
 
@@ -2684,9 +3459,25 @@ NOTE: By default `yaml_adapter_type` and `json_adapter_type` are set to
2684
3459
 
2685
3460
  Lutaml::Model supports the following XML adapters:
2686
3461
 
2687
- * Nokogiri (default)
2688
- * Oga (optional, plain Ruby suitable for Opal/JS)
2689
- * Ox (optional)
3462
+ Nokogiri::
3463
+ (default)
3464
+ Popular `libxml` based XML parser for Ruby.
3465
+ Requires native extensions (i.e. compiled C code).
3466
+ Requires the `nokogiri` gem.
3467
+
3468
+ Oga::
3469
+ (optional)
3470
+ Pure Ruby XML parser.
3471
+ Does not require native extensions and is suitable for
3472
+ https://opalrb.com[Opal] (Ruby on JavaScript).
3473
+ Requires the `oga` gem.
3474
+
3475
+ Ox::
3476
+ (optional)
3477
+ Fast XML parser and object serializer for Ruby, implemented partially in C.
3478
+ Requires native extensions (i.e. compiled C code).
3479
+ Requires the `ox` gem.
3480
+
2690
3481
 
2691
3482
  .Using the Nokogiri XML adapter
2692
3483
  [source,ruby]
@@ -2726,7 +3517,10 @@ end
2726
3517
 
2727
3518
  Lutaml::Model supports only one YAML adapter.
2728
3519
 
2729
- * YAML (default)
3520
+ YAML::
3521
+ (default)
3522
+ The Psych YAML parser and emitter for Ruby.
3523
+ Included in the Ruby standard library.
2730
3524
 
2731
3525
  .Using the YAML adapter
2732
3526
  [source,ruby]
@@ -2745,8 +3539,15 @@ end
2745
3539
 
2746
3540
  Lutaml::Model supports the following JSON adapters:
2747
3541
 
2748
- * JSON (default)
2749
- * MultiJson (optional)
3542
+ JSON::
3543
+ (default)
3544
+ The standard JSON library for Ruby.
3545
+ Included in the Ruby standard library.
3546
+
3547
+ MultiJson::
3548
+ (optional)
3549
+ A gem that provides a common interface to multiple JSON libraries.
3550
+ Requires the `multi_json` gem.
2750
3551
 
2751
3552
  .Using the JSON adapter
2752
3553
  [source,ruby]
@@ -2774,8 +3575,18 @@ end
2774
3575
 
2775
3576
  Lutaml::Model supports the following TOML adapters:
2776
3577
 
2777
- * Toml-rb (default)
2778
- * Tomlib (optional)
3578
+ Toml-rb::
3579
+ (default)
3580
+ A TOML parser and serializer for Ruby that is compatible with the TOML v1.0.0
3581
+ specification.
3582
+ Requires the `toml-rb` gem.
3583
+
3584
+ Tomlib::
3585
+ (optional)
3586
+ Toml-rb fork that is compatible with the TOML v1.0.0 specification, but with
3587
+ additional features.
3588
+ Requires the `tomlib` gem.
3589
+
2779
3590
 
2780
3591
  .Using the Toml-rb adapter
2781
3592
  [source,ruby]