lutaml-model 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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
 
@@ -2468,190 +3305,6 @@ end
2468
3305
  ====
2469
3306
 
2470
3307
 
2471
- [[attribute-extraction]]
2472
- ==== Attribute extraction (for key-value data models only)
2473
-
2474
- NOTE: This feature is for key-value data model serialization only.
2475
-
2476
- The `child_mappings` option is used to extract results from a key-value data
2477
- model (JSON, YAML, TOML) into a `Lutaml::Model` collection.
2478
-
2479
- The values are extracted from the key-value data model using the list of keys
2480
- provided.
2481
-
2482
- Syntax:
2483
-
2484
- [source,ruby]
2485
- ----
2486
- json | yaml | toml do
2487
- map 'key_value_model_attribute_name', to: :name_of_attribute,
2488
- child_mappings: {
2489
- key_attribute_name_1: <1>
2490
- {path_to_value_1}, <2>
2491
- key_attribute_name_2:
2492
- {path_to_value_2},
2493
- # ...
2494
- }
2495
- end
2496
- ----
2497
- <1> The `key_attribute_name_1` is the attribute name in the model. The value of
2498
- this attribute will be assigned the key of the hash in the key-value data model.
2499
-
2500
- <2> The `path_to_value_1` is an array of keys that represent the path to the
2501
- value in the key-value data model. The keys are used to extract the value from
2502
- the key-value data model and assign it to the attribute in the model.
2503
-
2504
- The `path_to_value` is in a nested array format with each value a symbol, where
2505
- each symbol represents a key to traverse down. The last key in the path is the
2506
- value to be extracted.
2507
-
2508
- .Determining the path to value in a key-value data model
2509
- [example]
2510
- ====
2511
- The following JSON contains 2 keys in schema named `engine` and `gearbox`.
2512
-
2513
- [source,json]
2514
- ----
2515
- {
2516
- "components": {
2517
- "engine": {
2518
- "manufacturer": "Ford",
2519
- "model": "V8"
2520
- },
2521
- "gearbox": {
2522
- "manufacturer": "Toyota",
2523
- "model": "4-speed"
2524
- }
2525
- }
2526
- }
2527
- ----
2528
-
2529
- The path to value for the `engine` schema is `[:components, :engine]` and for
2530
- the `gearbox` schema is `[:components, :gearbox]`.
2531
- ====
2532
-
2533
- In `path_to_value`, the `:key` and `:value` are reserved instructions used to
2534
- assign the key or value of the serialization data respectively as the value to
2535
- the attribute.
2536
-
2537
- [example]
2538
- ====
2539
- In the following JSON content, the `path_to_value` for the object keys named
2540
- `engine` and `gearbox` will utilize the `:key` keyword to assign the key of the
2541
- object as the value of a designated attribute.
2542
-
2543
- [source,json]
2544
- ----
2545
- {
2546
- "components": {
2547
- "engine": { /*...*/ },
2548
- "gearbox": { /*...*/ }
2549
- }
2550
- }
2551
- ----
2552
- ====
2553
-
2554
- If a specified value path is not found, the corresponding attribute in the model
2555
- will be assigned a `nil` value.
2556
-
2557
- .Attribute values set to `nil` when the `path_to_value` is not found
2558
- [example]
2559
- ====
2560
- In the following JSON content, the `path_to_value` of `[:extras, :sunroof]` and
2561
- `[:extras, :drinks_cooler]` at the object `"gearbox"` would be set to `nil`.
2562
-
2563
- [source,json]
2564
- ----
2565
- {
2566
- "components": {
2567
- "engine": {
2568
- "manufacturer": "Ford",
2569
- "extras": {
2570
- "sunroof": true,
2571
- "drinks_cooler": true
2572
- }
2573
- },
2574
- "gearbox": {
2575
- "manufacturer": "Toyota"
2576
- }
2577
- }
2578
- }
2579
- ----
2580
- ====
2581
-
2582
-
2583
- .Using the `child_mappings` option to extract values from a key-value data model
2584
- [example]
2585
- ====
2586
- The following JSON contains 2 keys in schema named `foo` and `bar`.
2587
-
2588
- [source,json]
2589
- ----
2590
- {
2591
- "schemas": {
2592
- "foo": { <1>
2593
- "path": { <2>
2594
- "link": "link one",
2595
- "name": "one"
2596
- }
2597
- },
2598
- "bar": { <1>
2599
- "path": { <2>
2600
- "link": "link two",
2601
- "name": "two"
2602
- }
2603
- }
2604
- }
2605
- }
2606
- ----
2607
- <1> The keys `foo` and `bar` are to be mapped to the `id` attribute.
2608
- <2> The nested `path.link` and `path.name` keys are used as the `link` and
2609
- `name` attributes, respectively.
2610
-
2611
- A model can be defined for this JSON as follows:
2612
-
2613
- [source,ruby]
2614
- ----
2615
- class Schema < Lutaml::Model::Serializable
2616
- attribute :id, :string
2617
- attribute :link, :string
2618
- attribute :name, :string
2619
- end
2620
-
2621
- class ChildMappingClass < Lutaml::Model::Serializable
2622
- attribute :schemas, Schema, collection: true
2623
-
2624
- json do
2625
- map "schemas", to: :schemas,
2626
- child_mappings: {
2627
- id: :key,
2628
- link: %i[path link],
2629
- name: %i[path name],
2630
- }
2631
- end
2632
- end
2633
- ----
2634
-
2635
- The output becomes:
2636
-
2637
- [source,ruby]
2638
- ----
2639
- > ChildMappingClass.from_json(json)
2640
- > #<ChildMappingClass:0x0000000104ac7240
2641
- @schemas=
2642
- [#<Schema:0x0000000104ac6e30 @id="foo", @link="link one", @name="one">,
2643
- #<Schema:0x0000000104ac58f0 @id="bar", @link="link two", @name="two">]>
2644
- > ChildMappingClass.new(schemas: [Schema.new(id: "foo", link: "link one", name: "one"), Schema.new(id: "bar", link: "link two", name: "two")]).to_json
2645
- > #{"schemas"=>{"foo"=>{"path"=>{"link"=>"link one", "name"=>"one"}}, {"bar"=>{"path"=>{"link"=>"link two", "name"=>"two"}}}}
2646
- ----
2647
-
2648
- In this example:
2649
-
2650
- * The `key` of each schema (`foo` and `bar`) is mapped to the `id` attribute.
2651
-
2652
- * The nested `path.link` and `path.name` keys are mapped to the `link` and
2653
- `name` attributes, respectively.
2654
- ====
2655
3308
 
2656
3309
 
2657
3310
  == Validation