lutaml-model 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +0 -5
- data/.rubocop_todo.yml +20 -101
- data/Gemfile +3 -18
- data/README.adoc +1100 -140
- data/lib/lutaml/model/attribute.rb +15 -2
- data/lib/lutaml/model/config.rb +0 -1
- data/lib/lutaml/model/error/invalid_value_error.rb +18 -0
- data/lib/lutaml/model/error.rb +8 -0
- data/lib/lutaml/model/json_adapter/json_document.rb +20 -0
- data/lib/lutaml/model/json_adapter/json_object.rb +28 -0
- data/lib/lutaml/model/json_adapter/{multi_json.rb → multi_json_adapter.rb} +2 -3
- data/lib/lutaml/model/json_adapter/{standard.rb → standard_json_adapter.rb} +2 -3
- data/lib/lutaml/model/json_adapter.rb +1 -31
- data/lib/lutaml/model/key_value_mapping.rb +9 -2
- data/lib/lutaml/model/key_value_mapping_rule.rb +0 -1
- data/lib/lutaml/model/mapping_hash.rb +0 -2
- data/lib/lutaml/model/mapping_rule.rb +5 -3
- data/lib/lutaml/model/schema/json_schema.rb +0 -1
- data/lib/lutaml/model/schema/relaxng_schema.rb +0 -1
- data/lib/lutaml/model/schema/xsd_schema.rb +0 -1
- data/lib/lutaml/model/schema/yaml_schema.rb +0 -1
- data/lib/lutaml/model/schema.rb +0 -1
- data/lib/lutaml/model/serializable.rb +0 -1
- data/lib/lutaml/model/serialize.rb +241 -153
- data/lib/lutaml/model/toml_adapter/toml_document.rb +20 -0
- data/lib/lutaml/model/toml_adapter/toml_object.rb +28 -0
- data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +4 -5
- data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +2 -3
- data/lib/lutaml/model/toml_adapter.rb +0 -31
- data/lib/lutaml/model/type/date_time.rb +20 -0
- data/lib/lutaml/model/type/json.rb +34 -0
- data/lib/lutaml/model/type/time_without_date.rb +4 -3
- data/lib/lutaml/model/type.rb +61 -124
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +20 -13
- data/lib/lutaml/model/xml_adapter/oga_adapter.rb +4 -5
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +24 -17
- data/lib/lutaml/model/xml_adapter/xml_attribute.rb +27 -0
- data/lib/lutaml/model/xml_adapter/xml_document.rb +184 -0
- data/lib/lutaml/model/xml_adapter/xml_element.rb +94 -0
- data/lib/lutaml/model/xml_adapter/xml_namespace.rb +49 -0
- data/lib/lutaml/model/xml_adapter.rb +0 -266
- data/lib/lutaml/model/xml_mapping.rb +1 -1
- data/lib/lutaml/model/xml_mapping_rule.rb +3 -4
- data/lib/lutaml/model/yaml_adapter/standard_yaml_adapter.rb +34 -0
- data/lib/lutaml/model/yaml_adapter/yaml_document.rb +20 -0
- data/lib/lutaml/model/yaml_adapter/yaml_object.rb +28 -0
- data/lib/lutaml/model/yaml_adapter.rb +1 -19
- data/lib/lutaml/model.rb +7 -5
- metadata +19 -5
- data/lib/lutaml/model/xml_namespace.rb +0 -47
data/README.adoc
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= LutaML Ruby modeller
|
1
|
+
= LutaML Ruby modeller
|
2
2
|
|
3
3
|
https://github.com/lutaml/lutaml-model[image:https://img.shields.io/github/stars/lutaml/lutaml-model.svg?style=social[GitHub Stars]]
|
4
4
|
https://github.com/lutaml/lutaml-model[image:https://img.shields.io/github/forks/lutaml/lutaml-model.svg?style=social[GitHub Forks]]
|
@@ -6,39 +6,41 @@ image:https://img.shields.io/github/license/lutaml/lutaml-model.svg[License]
|
|
6
6
|
image:https://img.shields.io/github/actions/workflow/status/lutaml/lutaml-model/test.yml?branch=main[Build Status]
|
7
7
|
image:https://img.shields.io/gem/v/lutaml-model.svg[RubyGems Version]
|
8
8
|
|
9
|
-
==
|
9
|
+
== Purpose
|
10
10
|
|
11
11
|
Lutaml::Model is a lightweight library for serializing and deserializing Ruby
|
12
12
|
objects to and from various formats such as JSON, XML, YAML, and TOML. It uses
|
13
13
|
an adapter pattern to support multiple libraries for each format, providing
|
14
14
|
flexibility and extensibility for your data modeling needs.
|
15
15
|
|
16
|
-
The name "LutaML" comes from the Latin word "Lutum
|
16
|
+
The name "LutaML" comes from the Latin word for clay, "Lutum", and "ML"
|
17
17
|
for Markup Language. Just as clay can be molded and modeled into beautiful and
|
18
18
|
practical end products, the Lutaml::Model gem is used for data modeling,
|
19
19
|
allowing you to shape and structure your data into useful forms.
|
20
20
|
|
21
21
|
|
22
|
+
NOTE: Lutaml::Model is designed to be compatible with the
|
23
|
+
https://www.shalerb.org[Shale] data modeling API. Shale is an amazing Ruby data
|
24
|
+
modeller. Lutaml::Model is meant to address needs that are not currently
|
25
|
+
addressed by Shale.
|
22
26
|
|
23
|
-
NOTE: Lutaml::Model is designed to be compatible with the Shale data modeling
|
24
|
-
API. Shale is an amazing Ruby data modeller. Lutaml::Model is meant to address
|
25
|
-
needs that are not currently addresed by Shale.
|
26
27
|
|
27
|
-
==
|
28
|
+
== Data modeling in a nutshell
|
28
29
|
|
29
30
|
Data modeling is the process of creating a data model for the data to be stored
|
30
31
|
in a database or used in an application. It helps in defining the structure,
|
31
32
|
relationships, and constraints of the data, making it easier to manage and use.
|
32
33
|
|
33
34
|
Lutaml::Model simplifies data modeling in Ruby by allowing you to define models
|
34
|
-
with attributes and serialize/deserialize them to/from various
|
35
|
-
seamlessly.
|
35
|
+
with attributes and serialize/deserialize them to/from various serialization
|
36
|
+
formats seamlessly.
|
37
|
+
|
36
38
|
|
37
39
|
== Features
|
38
40
|
|
39
41
|
* Define models with attributes and types
|
40
42
|
* Serialize and deserialize models to/from JSON, XML, YAML, and TOML
|
41
|
-
* Support for multiple libraries (e.g., `toml-rb`, `tomlib`)
|
43
|
+
* Support for multiple serialization libraries (e.g., `toml-rb`, `tomlib`)
|
42
44
|
* Configurable adapters for different serialization formats
|
43
45
|
* Support for collections and default values
|
44
46
|
* Custom serialization/deserialization methods
|
@@ -67,9 +69,21 @@ Or install it yourself as:
|
|
67
69
|
gem install lutaml-model
|
68
70
|
----
|
69
71
|
|
70
|
-
==
|
72
|
+
== Defining a data model class
|
73
|
+
|
74
|
+
=== General
|
75
|
+
|
76
|
+
There are two ways to define a data model in Lutaml::Model:
|
77
|
+
|
78
|
+
* Inheriting from the `Lutaml::Model::Serializable` class
|
79
|
+
* Including the `Lutaml::Model::Serialize` module
|
71
80
|
|
72
|
-
|
81
|
+
=== Definition through inheritance
|
82
|
+
|
83
|
+
The simplest way to define a model is to create a class that inherits from
|
84
|
+
`Lutaml::Model::Serializable`.
|
85
|
+
|
86
|
+
The `attribute` class method is used to define attributes.
|
73
87
|
|
74
88
|
[source,ruby]
|
75
89
|
----
|
@@ -82,203 +96,461 @@ class Kiln < Lutaml::Model::Serializable
|
|
82
96
|
end
|
83
97
|
----
|
84
98
|
|
85
|
-
|
99
|
+
=== Definition through inclusion
|
100
|
+
|
101
|
+
If the model class already has a super class that it inherits from, the model
|
102
|
+
can be extended using the `Lutaml::Model::Serialize` module.
|
103
|
+
|
104
|
+
[source,ruby]
|
105
|
+
----
|
106
|
+
require 'lutaml/model'
|
107
|
+
|
108
|
+
class Kiln < SomeSuperClass
|
109
|
+
include Lutaml::Model::Serialize
|
110
|
+
|
111
|
+
attribute :brand, Lutaml::Model::Type::String
|
112
|
+
attribute :capacity, Lutaml::Model::Type::Integer
|
113
|
+
attribute :temperature, Lutaml::Model::Type::Integer
|
114
|
+
end
|
115
|
+
----
|
116
|
+
|
117
|
+
== Defining attributes
|
86
118
|
|
87
|
-
|
88
|
-
formats including XML, JSON, YAML, and TOML.
|
119
|
+
=== Supported attribute value types
|
89
120
|
|
90
|
-
|
121
|
+
Lutaml::Model supports the following attribute types, they can be
|
122
|
+
referred by a string, a symbol, or their class constant.
|
91
123
|
|
92
|
-
|
124
|
+
Syntax:
|
93
125
|
|
94
126
|
[source,ruby]
|
95
127
|
----
|
96
|
-
|
97
|
-
|
98
|
-
attribute :value, Lutaml::Model::Type::Integer
|
128
|
+
attribute :name_of_attribute, {symbol | string | class}
|
129
|
+
----
|
99
130
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
131
|
+
|===
|
132
|
+
| String | Symbol | Class name | Actual value class
|
133
|
+
|
134
|
+
| `String` | `:string` | `Lutaml::Model::Type::String` | `::String`
|
135
|
+
| `Integer` | `:integer` | `Lutaml::Model::Type::Integer` | `::Integer`
|
136
|
+
| `Float` | `:float` | `Lutaml::Model::Type::Float` | `::Float`
|
137
|
+
| `Date` | `:date` | `Lutaml::Model::Type::Date` | `::Date`
|
138
|
+
| `Time` | `:time` | `Lutaml::Model::Type::Time` | `::Time`
|
139
|
+
| `DateTime` | `:date_time` | `Lutaml::Model::Type::DateTime` | `::DateTime`
|
140
|
+
| `TimeWithoutDate` | `:time_without_date` | `Lutaml::Model::Type::TimeWithoutDate` | `::Time`
|
141
|
+
| `Boolean` | `:boolean` | `Lutaml::Model::Type::Boolean` | `Boolean`
|
142
|
+
| `Decimal` | `:decimal` | `Lutaml::Model::Type::Decimal` | `::BigDecimal`
|
143
|
+
| `Hash` | `:hash` | `Lutaml::Model::Type::Hash` | `::Hash`
|
144
|
+
| `Uuid` | `:uuid` | `Lutaml::Model::Type::Uuid` | `::String`
|
145
|
+
| `Symbol` | `:symbol` | `Lutaml::Model::Type::Symbol` | `Symbol`
|
146
|
+
| `Binary` | `:binary` | `Lutaml::Model::Type::Binary` | `Binary`
|
147
|
+
| `Url` | `:url` | `Lutaml::Model::Type::Url` | `::URI`
|
148
|
+
| `IpAddress` | `:ip_address` | `Lutaml::Model::Type::IpAddress` | `::IPAddr`
|
149
|
+
| `Json` | `:json` | `Lutaml::Model::Type::Json` | `::JSON`
|
150
|
+
|
151
|
+
|===
|
152
|
+
|
153
|
+
|
154
|
+
.Defining attributes with supported types via symbol, string and class
|
155
|
+
[example]
|
156
|
+
====
|
157
|
+
[source,ruby]
|
158
|
+
----
|
159
|
+
class Studio < Lutaml::Model::Serializable
|
160
|
+
# The following are equivalent
|
161
|
+
attribute :location, :string
|
162
|
+
attribute :potter, "String"
|
163
|
+
attribute :kiln, Lutaml::Model::Type::String
|
164
|
+
end
|
165
|
+
----
|
166
|
+
|
167
|
+
[source,ruby]
|
168
|
+
----
|
169
|
+
> s = Studio.new(location: 'London', potter: 'John Doe', kiln: 'Kiln 1')
|
170
|
+
> # <Studio:0x0000000104ac7240 @location="London", @potter="John Doe", @kiln="Kiln 1">
|
171
|
+
> s.location
|
172
|
+
> # "London"
|
173
|
+
> s.potter
|
174
|
+
> # "John Doe"
|
175
|
+
> s.kiln
|
176
|
+
> # "Kiln 1"
|
177
|
+
----
|
178
|
+
====
|
179
|
+
|
180
|
+
|
181
|
+
=== Attribute as a collection
|
182
|
+
|
183
|
+
Define attributes as collections (arrays or hashes) to store multiple values
|
184
|
+
using the `collection` option.
|
185
|
+
|
186
|
+
Syntax:
|
187
|
+
|
188
|
+
[source,ruby]
|
189
|
+
----
|
190
|
+
attribute :name_of_attribute, Type, collection: true
|
191
|
+
----
|
192
|
+
|
193
|
+
.Using the `collection` option to define a collection attribute
|
194
|
+
[example]
|
195
|
+
====
|
196
|
+
[source,ruby]
|
197
|
+
----
|
198
|
+
class Studio < Lutaml::Model::Serializable
|
199
|
+
attribute :location, Lutaml::Model::Type::String
|
200
|
+
attribute :potters, Lutaml::Model::Type::String, collection: true
|
201
|
+
end
|
202
|
+
----
|
203
|
+
|
204
|
+
[source,ruby]
|
205
|
+
----
|
206
|
+
> Studio.new.potters
|
207
|
+
> # []
|
208
|
+
> Studio.new(potters: ['John Doe', 'Jane Doe']).potters
|
209
|
+
> # ['John Doe', 'Jane Doe']
|
210
|
+
----
|
211
|
+
====
|
212
|
+
|
213
|
+
=== Attribute as an enumeration
|
214
|
+
|
215
|
+
An attribute can be defined as an enumeration by using the `values` directive.
|
216
|
+
|
217
|
+
The `values` directive is used to define acceptable values in an attribute. If
|
218
|
+
any other value is given, a `Lutaml::Model::InvalidValueError` will be raised.
|
219
|
+
|
220
|
+
Syntax:
|
221
|
+
|
222
|
+
[source,ruby]
|
223
|
+
----
|
224
|
+
attribute :name_of_attribute, Type, values: [value1, value2, ...]
|
225
|
+
----
|
226
|
+
|
227
|
+
.Using the `values` directive to define acceptable values for an attribute
|
228
|
+
[example]
|
229
|
+
====
|
230
|
+
[source,ruby]
|
231
|
+
----
|
232
|
+
class Ceramic < Lutaml::Model::Serializable
|
233
|
+
attribute :type, Lutaml::Model::Type::String,
|
234
|
+
values: ['Porcelain', 'Earthenware', 'Stoneware']
|
235
|
+
end
|
236
|
+
----
|
237
|
+
|
238
|
+
[source,ruby]
|
239
|
+
----
|
240
|
+
> Ceramic.new(type: 'Porcelain').type
|
241
|
+
> # "Porcelain"
|
242
|
+
> Ceramic.new(type: 'Earthenware').type
|
243
|
+
> # "Earthenware"
|
244
|
+
> Ceramic.new(type: 'Bone China').type
|
245
|
+
> # Lutaml::Model::InvalidValueError: Invalid value for attribute 'type'
|
246
|
+
----
|
247
|
+
====
|
248
|
+
|
249
|
+
|
250
|
+
=== Attribute value default
|
251
|
+
|
252
|
+
Specify default values for attributes using the `default` option.
|
253
|
+
The `default` option can be set to a value or a lambda that returns a value.
|
254
|
+
|
255
|
+
Syntax:
|
256
|
+
|
257
|
+
[source,ruby]
|
258
|
+
----
|
259
|
+
attribute :name_of_attribute, Type, default: -> { value }
|
260
|
+
----
|
261
|
+
|
262
|
+
|
263
|
+
.Using the `default` option to set a default value for an attribute
|
264
|
+
[example]
|
265
|
+
====
|
266
|
+
[source,ruby]
|
267
|
+
----
|
268
|
+
class Glaze < Lutaml::Model::Serializable
|
269
|
+
attribute :color, Lutaml::Model::Type::String, default: -> { 'Clear' }
|
270
|
+
attribute :temperature, Lutaml::Model::Type::Integer, default: -> { 1050 }
|
105
271
|
end
|
106
272
|
----
|
107
273
|
|
108
|
-
|
274
|
+
[source,ruby]
|
275
|
+
----
|
276
|
+
> Glaze.new.color
|
277
|
+
> # "Clear"
|
278
|
+
> Glaze.new.temperature
|
279
|
+
> # 1050
|
280
|
+
----
|
281
|
+
====
|
282
|
+
|
283
|
+
== Serialization model mappings
|
284
|
+
|
285
|
+
=== General
|
109
286
|
|
110
|
-
|
287
|
+
Lutaml::Model allows you to translate a data model into serialization models of
|
288
|
+
various serialization formats including XML, JSON, YAML, and TOML.
|
111
289
|
|
112
|
-
|
290
|
+
Depending on the serialization format, different methods are supported for
|
291
|
+
defining serialization and deserialization mappings.
|
113
292
|
|
293
|
+
Serialization model mappings are defined under the `xml`, `json`, `yaml`, and
|
294
|
+
`toml` blocks.
|
295
|
+
|
296
|
+
.Using the `xml`, `json`, `yaml`, and `toml` blocks to define serialization mappings
|
114
297
|
[source,ruby]
|
115
298
|
----
|
116
299
|
class Example < Lutaml::Model::Serializable
|
117
|
-
|
118
|
-
|
300
|
+
xml do
|
301
|
+
# ...
|
302
|
+
end
|
119
303
|
|
120
304
|
json do
|
121
|
-
|
122
|
-
map 'value', to: :value
|
305
|
+
# ...
|
123
306
|
end
|
124
307
|
|
125
308
|
yaml do
|
126
|
-
|
127
|
-
map 'value', to: :value
|
309
|
+
# ...
|
128
310
|
end
|
129
311
|
|
130
312
|
toml do
|
131
|
-
|
132
|
-
map 'value', to: :value
|
313
|
+
# ...
|
133
314
|
end
|
134
315
|
end
|
135
316
|
----
|
136
317
|
|
137
|
-
|
318
|
+
=== XML
|
319
|
+
|
320
|
+
==== Setting root element name
|
138
321
|
|
139
|
-
|
322
|
+
The `root` method sets the root element tag name of the XML document.
|
140
323
|
|
141
|
-
|
324
|
+
If `root` is not given, then the snake-cased class name will be used as the
|
325
|
+
root.
|
142
326
|
|
143
|
-
|
327
|
+
[example]
|
328
|
+
Sets the tag name for `<example>` in XML `<example>...</example>`.
|
329
|
+
|
330
|
+
Syntax:
|
144
331
|
|
145
332
|
[source,ruby]
|
146
333
|
----
|
147
|
-
|
148
|
-
|
149
|
-
|
334
|
+
xml do
|
335
|
+
root 'xml_element_name'
|
336
|
+
end
|
337
|
+
----
|
150
338
|
|
339
|
+
.Setting the root element name to `example`
|
340
|
+
[example]
|
341
|
+
====
|
342
|
+
[source,ruby]
|
343
|
+
----
|
344
|
+
class Example < Lutaml::Model::Serializable
|
151
345
|
xml do
|
152
346
|
root 'example'
|
153
|
-
map_element 'name', to: :name
|
154
|
-
map_content to: :description
|
155
347
|
end
|
156
348
|
end
|
157
349
|
----
|
158
350
|
|
159
|
-
|
351
|
+
[source,ruby]
|
352
|
+
----
|
353
|
+
> Example.new.to_xml
|
354
|
+
> #<example></example>
|
355
|
+
----
|
356
|
+
====
|
357
|
+
|
358
|
+
==== Mapping elements
|
359
|
+
|
360
|
+
The `map_element` method maps an XML element to a data model attribute.
|
160
361
|
|
161
|
-
|
362
|
+
[example]
|
363
|
+
To handle the `<name>` tag in `<example><name>John Doe</name></example>`.
|
364
|
+
The value will be set to `John Doe`.
|
365
|
+
|
366
|
+
Syntax:
|
367
|
+
|
368
|
+
[source,ruby]
|
369
|
+
----
|
370
|
+
xml do
|
371
|
+
map_element 'xml_element_name', to: :name_of_attribute
|
372
|
+
end
|
373
|
+
----
|
162
374
|
|
375
|
+
.Mapping the `name` tag to the `name` attribute
|
376
|
+
[example]
|
377
|
+
====
|
163
378
|
[source,ruby]
|
164
379
|
----
|
165
380
|
class Example < Lutaml::Model::Serializable
|
166
381
|
attribute :name, Lutaml::Model::Type::String
|
167
|
-
attribute :value, Lutaml::Model::Type::Integer
|
168
382
|
|
169
|
-
|
170
|
-
|
171
|
-
|
383
|
+
xml do
|
384
|
+
root 'example'
|
385
|
+
map_element 'name', to: :name
|
172
386
|
end
|
173
387
|
end
|
174
388
|
----
|
175
389
|
|
176
|
-
|
177
|
-
|
178
|
-
|
390
|
+
[source,xml]
|
391
|
+
----
|
392
|
+
<example><name>John Doe</name></example>
|
393
|
+
----
|
179
394
|
|
180
395
|
[source,ruby]
|
181
396
|
----
|
182
|
-
|
183
|
-
|
184
|
-
|
397
|
+
> Example.from_xml(xml)
|
398
|
+
> #<Example:0x0000000104ac7240 @name="John Doe">
|
399
|
+
> Example.new(name: "John Doe").to_xml
|
400
|
+
> #<example><name>John Doe</name></example>
|
401
|
+
----
|
402
|
+
====
|
185
403
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
404
|
+
==== Mapping attributes
|
405
|
+
|
406
|
+
The `map_attribute` method maps an XML attribute to a data model attribute.
|
407
|
+
|
408
|
+
Syntax:
|
409
|
+
|
410
|
+
[source,ruby]
|
411
|
+
----
|
412
|
+
xml do
|
413
|
+
map_attribute 'xml_attribute_name', to: :name_of_attribute
|
190
414
|
end
|
191
415
|
----
|
192
416
|
|
193
|
-
|
194
|
-
|
195
|
-
|
417
|
+
.Using `map_attribute` to map the `value` attribute
|
418
|
+
[example]
|
419
|
+
====
|
420
|
+
The following class will parse the XML snippet below:
|
196
421
|
|
197
422
|
[source,ruby]
|
198
423
|
----
|
199
424
|
class Example < Lutaml::Model::Serializable
|
200
|
-
attribute :name, Lutaml::Model::Type::String
|
201
425
|
attribute :value, Lutaml::Model::Type::Integer
|
202
426
|
|
203
|
-
|
204
|
-
|
205
|
-
|
427
|
+
xml do
|
428
|
+
root 'example'
|
429
|
+
map_attribute 'value', to: :value
|
206
430
|
end
|
207
431
|
end
|
208
432
|
----
|
209
433
|
|
210
|
-
|
211
|
-
|
212
|
-
|
434
|
+
[source,xml]
|
435
|
+
----
|
436
|
+
<example value=12><name>John Doe</name></example>
|
437
|
+
----
|
213
438
|
|
214
439
|
[source,ruby]
|
215
440
|
----
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
441
|
+
> Example.from_xml(xml)
|
442
|
+
> #<Example:0x0000000104ac7240 @value=12>
|
443
|
+
> Example.new(value: 12).to_xml
|
444
|
+
> #<example value="12"></example>
|
220
445
|
----
|
446
|
+
====
|
221
447
|
|
222
|
-
== Attribute Defaults Using the `default` Option
|
223
448
|
|
224
|
-
|
449
|
+
==== Mapping content
|
450
|
+
|
451
|
+
Content represents the text inside an XML element, inclusive of whitespace.
|
452
|
+
|
453
|
+
The `map_content` method maps an XML element's content to a data model
|
454
|
+
attribute.
|
455
|
+
|
456
|
+
Syntax:
|
225
457
|
|
226
458
|
[source,ruby]
|
227
459
|
----
|
228
|
-
|
229
|
-
|
230
|
-
attribute :temperature, Lutaml::Model::Type::Integer, default: -> { 1050 }
|
460
|
+
xml do
|
461
|
+
map_content to: :name_of_attribute
|
231
462
|
end
|
232
463
|
----
|
233
464
|
|
234
|
-
|
235
|
-
|
236
|
-
|
465
|
+
.Using `map_content` to map content of the `description` tag
|
466
|
+
[example]
|
467
|
+
====
|
468
|
+
The following class will parse the XML snippet below:
|
237
469
|
|
238
470
|
[source,ruby]
|
239
471
|
----
|
240
|
-
class
|
241
|
-
attribute :
|
242
|
-
attribute :glaze, Glaze
|
472
|
+
class Example < Lutaml::Model::Serializable
|
473
|
+
attribute :description, Lutaml::Model::Type::String
|
243
474
|
|
244
|
-
|
245
|
-
|
246
|
-
|
475
|
+
xml do
|
476
|
+
root 'example'
|
477
|
+
map_content to: :description
|
247
478
|
end
|
248
479
|
end
|
249
480
|
----
|
250
481
|
|
251
|
-
|
482
|
+
[source,xml]
|
483
|
+
----
|
484
|
+
<example>John Doe is my moniker.</example>
|
485
|
+
----
|
486
|
+
|
487
|
+
[source,ruby]
|
488
|
+
----
|
489
|
+
> Example.from_xml(xml)
|
490
|
+
> #<Example:0x0000000104ac7240 @description="John Doe is my moniker.">
|
491
|
+
> Example.new(description: "John Doe is my moniker.").to_xml
|
492
|
+
> #<example>John Doe is my moniker.</example>
|
493
|
+
----
|
494
|
+
====
|
495
|
+
|
496
|
+
|
252
497
|
|
253
|
-
|
498
|
+
==== Example for mapping
|
499
|
+
|
500
|
+
[example]
|
501
|
+
====
|
502
|
+
The following class will parse the XML snippet below:
|
254
503
|
|
255
504
|
[source,ruby]
|
256
505
|
----
|
257
|
-
class
|
506
|
+
class Example < Lutaml::Model::Serializable
|
258
507
|
attribute :name, Lutaml::Model::Type::String
|
259
|
-
attribute :
|
508
|
+
attribute :description, Lutaml::Model::Type::String
|
509
|
+
attribute :value, Lutaml::Model::Type::Integer
|
260
510
|
|
261
|
-
|
262
|
-
|
263
|
-
|
511
|
+
xml do
|
512
|
+
root 'example'
|
513
|
+
map_element 'name', to: :name
|
514
|
+
map_attribute 'value', to: :value
|
515
|
+
map_content to: :description
|
264
516
|
end
|
517
|
+
end
|
518
|
+
----
|
265
519
|
|
266
|
-
|
267
|
-
|
268
|
-
|
520
|
+
[source,xml]
|
521
|
+
----
|
522
|
+
<example value=12><name>John Doe</name> is my moniker.</example>
|
523
|
+
----
|
269
524
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
525
|
+
[source,ruby]
|
526
|
+
----
|
527
|
+
> Example.from_xml(xml)
|
528
|
+
> #<Example:0x0000000104ac7240 @name="John Doe", @description=" is my moniker.", @value=12>
|
529
|
+
> Example.new(name: "John Doe", description: " is my moniker.", value: 12).to_xml
|
530
|
+
> #<example value="12"><name>John Doe</name> is my moniker.</example>
|
274
531
|
----
|
532
|
+
====
|
275
533
|
|
276
|
-
== Using XML Namespaces
|
277
534
|
|
278
|
-
|
535
|
+
==== Namespaces
|
279
536
|
|
280
|
-
|
537
|
+
===== Namespace at root
|
281
538
|
|
539
|
+
The `namespace` method in the `xml` block sets the namespace for the root
|
540
|
+
element.
|
541
|
+
|
542
|
+
Syntax:
|
543
|
+
|
544
|
+
[source,ruby]
|
545
|
+
----
|
546
|
+
xml do
|
547
|
+
namespace 'http://example.com/namespace'
|
548
|
+
end
|
549
|
+
----
|
550
|
+
|
551
|
+
.Using the `namespace` method to set the namespace for the root element
|
552
|
+
[example]
|
553
|
+
====
|
282
554
|
[source,ruby]
|
283
555
|
----
|
284
556
|
class Ceramic < Lutaml::Model::Serializable
|
@@ -294,9 +566,44 @@ class Ceramic < Lutaml::Model::Serializable
|
|
294
566
|
end
|
295
567
|
----
|
296
568
|
|
297
|
-
|
569
|
+
[source,xml]
|
570
|
+
----
|
571
|
+
<Ceramic xmlns='http://example.com/ceramic'><Type>Porcelain</Type><Glaze>Clear</Glaze></Ceramic>
|
572
|
+
----
|
573
|
+
|
574
|
+
[source,ruby]
|
575
|
+
----
|
576
|
+
> Ceramic.from_xml(xml_file)
|
577
|
+
> #<Ceramic:0x0000000104ac7240 @type="Porcelain", @glaze="Clear">
|
578
|
+
> Ceramic.new(type: "Porcelain", glaze: "Clear").to_xml
|
579
|
+
> #<Ceramic xmlns="http://example.com/ceramic"><Type>Porcelain</Type><Glaze>Clear</Glaze></Ceramic>
|
580
|
+
----
|
581
|
+
====
|
582
|
+
|
583
|
+
===== Namespace on attribute
|
584
|
+
|
585
|
+
If the namespace is defined on an XML attribute, then that will be given
|
586
|
+
priority over the one defined in the class.
|
587
|
+
|
588
|
+
Syntax:
|
589
|
+
|
590
|
+
[source,ruby]
|
591
|
+
----
|
592
|
+
xml do
|
593
|
+
map_element 'xml_element_name', to: :name_of_attribute,
|
594
|
+
namespace: 'http://example.com/namespace',
|
595
|
+
prefix: 'prefix'
|
596
|
+
end
|
597
|
+
----
|
598
|
+
|
599
|
+
`namespace`:: The XML namespace used by this element
|
600
|
+
`prefix`:: The XML namespace prefix used by this element (optional)
|
298
601
|
|
299
|
-
|
602
|
+
.Using the `namespace` option to set the namespace for an element
|
603
|
+
[example]
|
604
|
+
====
|
605
|
+
In this example, `glz` will be used for `Glaze` if it is added inside the
|
606
|
+
`Ceramic` class, and `glaze` will be used otherwise.
|
300
607
|
|
301
608
|
[source,ruby]
|
302
609
|
----
|
@@ -326,33 +633,125 @@ class Ceramic < Lutaml::Model::Serializable
|
|
326
633
|
end
|
327
634
|
----
|
328
635
|
|
329
|
-
|
636
|
+
[source,xml]
|
637
|
+
----
|
638
|
+
<Ceramic xmlns='http://example.com/ceramic'>
|
639
|
+
<Type>Porcelain</Type>
|
640
|
+
<glz:Glaze xmlns='http://example.com/glaze'>
|
641
|
+
<color>Clear</color>
|
642
|
+
<temperature>1050</temperature>
|
643
|
+
</glz:Glaze>
|
644
|
+
</Ceramic>
|
645
|
+
----
|
330
646
|
|
331
647
|
[source,ruby]
|
332
648
|
----
|
333
|
-
|
334
|
-
|
335
|
-
|
649
|
+
> Ceramic.from_xml(xml_file)
|
650
|
+
> #<Ceramic:0x0000000104ac7240 @type="Porcelain", @glaze=#<Glaze:0x0000000104ac7240 @color="Clear", @temperature=1050>>
|
651
|
+
> Ceramic.new(type: "Porcelain", glaze: Glaze.new(color: "Clear", temperature: 1050)).to_xml
|
652
|
+
> #<Ceramic xmlns="http://example.com/ceramic"><Type>Porcelain</Type><glz:Glaze xmlns="http://example.com/glaze"><color>Clear</color><temperature>1050</temperature></glz:Glaze></Ceramic>
|
653
|
+
----
|
654
|
+
====
|
336
655
|
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
656
|
+
===== Namespace with `inherit` option
|
657
|
+
|
658
|
+
The `inherit` option is used at the element level to inherit the namespace from
|
659
|
+
the root element.
|
660
|
+
|
661
|
+
Syntax:
|
662
|
+
|
663
|
+
[source,ruby]
|
664
|
+
----
|
665
|
+
xml do
|
666
|
+
map_element 'xml_element_name', to: :name_of_attribute, namespace: :inherit
|
667
|
+
end
|
668
|
+
----
|
669
|
+
|
670
|
+
.Using the `inherit` option to inherit the namespace from the root element
|
671
|
+
[example]
|
672
|
+
====
|
673
|
+
In this example, the `Type` element will inherit the namespace from the root.
|
674
|
+
|
675
|
+
[source,ruby]
|
676
|
+
----
|
677
|
+
class Ceramic < Lutaml::Model::Serializable
|
678
|
+
attribute :type, Lutaml::Model::Type::String
|
679
|
+
attribute :glaze, Lutaml::Model::Type::String
|
680
|
+
attribute :color, Lutaml::Model::Type::String
|
681
|
+
|
682
|
+
xml do
|
683
|
+
root 'Ceramic'
|
684
|
+
namespace 'http://example.com/ceramic', prefix: 'cera'
|
685
|
+
map_element 'Type', to: :type, namespace: :inherit
|
341
686
|
map_element 'Glaze', to: :glaze
|
687
|
+
map_attribute 'color', to: :color, namespace: 'http://example.com/color', prefix: 'clr'
|
342
688
|
end
|
343
689
|
end
|
344
690
|
----
|
345
691
|
|
346
|
-
|
692
|
+
[source,xml]
|
693
|
+
----
|
694
|
+
<Ceramic
|
695
|
+
xmlns:cera='http://example.com/ceramic'
|
696
|
+
xmlns:clr='http://example.com/color'
|
697
|
+
clr:color="navy-blue">
|
698
|
+
<cera:Type>Porcelain</cera:Type>
|
699
|
+
<Glaze>Clear</Glaze>
|
700
|
+
</Ceramic>
|
701
|
+
----
|
702
|
+
|
703
|
+
[source,ruby]
|
704
|
+
----
|
705
|
+
> Ceramic.from_xml(xml_file)
|
706
|
+
> #<Ceramic:0x0000000104ac7240 @type="Porcelain", @glaze="Clear", @color="navy-blue">
|
707
|
+
> Ceramic.new(type: "Porcelain", glaze: "Clear", color: "navy-blue").to_xml
|
708
|
+
> #<Ceramic xmlns:cera="http://example.com/ceramic"
|
709
|
+
# xmlns:clr='http://example.com/color'
|
710
|
+
# clr:color="navy-blue">
|
711
|
+
# <cera:Type>Porcelain</cera:Type>
|
712
|
+
# <Glaze>Clear</Glaze>
|
713
|
+
# </Ceramic>
|
714
|
+
----
|
715
|
+
====
|
716
|
+
|
347
717
|
|
348
|
-
|
718
|
+
==== Mixed content
|
719
|
+
|
720
|
+
===== General
|
721
|
+
|
722
|
+
In XML there can be tags that contain content mixed with other tags and where
|
723
|
+
whitespace is significant, such as to represent rich text.
|
724
|
+
|
725
|
+
[example]
|
726
|
+
====
|
727
|
+
[source,xml]
|
728
|
+
----
|
729
|
+
<description><p>My name is <bold>John Doe</bold>, and I'm <i>28</i> years old</p></description>
|
730
|
+
----
|
731
|
+
====
|
349
732
|
|
350
|
-
To map this to Lutaml::Model we can use the mixed option
|
733
|
+
To map this to Lutaml::Model we can use the `mixed` option in either way:
|
351
734
|
|
352
|
-
|
735
|
+
* when defining the model;
|
736
|
+
* when referencing the model.
|
353
737
|
|
354
|
-
This will always treat the content of `<p>` tag as mixed content.
|
355
738
|
|
739
|
+
===== Specifying the `mixed` option at `root`
|
740
|
+
|
741
|
+
This will always treat the content of the element itself as mixed content.
|
742
|
+
|
743
|
+
Syntax:
|
744
|
+
|
745
|
+
[source,ruby]
|
746
|
+
----
|
747
|
+
xml do
|
748
|
+
root 'xml_element_name', mixed: true
|
749
|
+
end
|
750
|
+
----
|
751
|
+
|
752
|
+
.Applying `mixed` to treat root as mixed content
|
753
|
+
[example]
|
754
|
+
====
|
356
755
|
[source,ruby]
|
357
756
|
----
|
358
757
|
class Paragraph < Lutaml::Model::Serializable
|
@@ -368,10 +767,35 @@ class Paragraph < Lutaml::Model::Serializable
|
|
368
767
|
end
|
369
768
|
----
|
370
769
|
|
371
|
-
|
770
|
+
[source,ruby]
|
771
|
+
----
|
772
|
+
> Paragraph.from_xml("<p>My name is <bold>John Doe</bold>, and I'm <i>28</i> years old</p>")
|
773
|
+
> #<Paragraph:0x0000000104ac7240 @bold="John Doe", @italic="28">
|
774
|
+
> Paragraph.new(bold: "John Doe", italic: "28").to_xml
|
775
|
+
> #<p>My name is <bold>John Doe</bold>, and I'm <i>28</i> years old</p>
|
776
|
+
----
|
777
|
+
====
|
778
|
+
|
779
|
+
TODO: How to create mixed content from `#new`?
|
780
|
+
|
372
781
|
|
373
|
-
|
782
|
+
===== Specifying the `mixed` option when referencing a model
|
783
|
+
|
784
|
+
This will only treat the content of the referenced model as mixed content if the
|
785
|
+
`mixed: true` is added when referencing it.
|
786
|
+
|
787
|
+
Syntax:
|
374
788
|
|
789
|
+
[source,ruby]
|
790
|
+
----
|
791
|
+
xml do
|
792
|
+
map_element 'xml_element_name', to: :name_of_attribute, mixed: true
|
793
|
+
end
|
794
|
+
----
|
795
|
+
|
796
|
+
.Applying `mixed` to treat an inner element as mixed content
|
797
|
+
[example]
|
798
|
+
====
|
375
799
|
[source,ruby]
|
376
800
|
----
|
377
801
|
class Paragraph < Lutaml::Model::Serializable
|
@@ -397,55 +821,591 @@ class Description < Lutaml::Model::Serializable
|
|
397
821
|
end
|
398
822
|
----
|
399
823
|
|
824
|
+
[source,ruby]
|
825
|
+
----
|
826
|
+
> Description.from_xml("<description><p>My name is <bold>John Doe</bold>, and I'm <i>28</i> years old</p></description>")
|
827
|
+
> #<Description:0x0000000104ac7240 @paragraph=#<Paragraph:0x0000000104ac7240 @bold="John Doe", @italic="28">>
|
828
|
+
> Description.new(paragraph: Paragraph.new(bold: "John Doe", italic: "28")).to_xml
|
829
|
+
> #<description><p>My name is <bold>John Doe</bold>, and I'm <i>28</i> years old</p></description>
|
830
|
+
----
|
831
|
+
====
|
832
|
+
|
833
|
+
|
834
|
+
=== Key value data models
|
835
|
+
|
836
|
+
==== General
|
837
|
+
|
838
|
+
Key-value data models like JSON, YAML, and TOML all share a similar structure
|
839
|
+
where data is stored as key-value pairs.
|
840
|
+
|
841
|
+
Lutaml::Model works with these formats in a similar way.
|
842
|
+
|
843
|
+
==== Mapping
|
844
|
+
|
845
|
+
The `map` method is used to define key-value mappings.
|
846
|
+
|
847
|
+
Syntax:
|
848
|
+
|
849
|
+
[source,ruby]
|
850
|
+
----
|
851
|
+
json | yaml | toml do
|
852
|
+
map 'key_value_model_attribute_name', to: :name_of_attribute
|
853
|
+
end
|
854
|
+
----
|
855
|
+
|
856
|
+
.Using the `map` method to define key-value mappings
|
857
|
+
[example]
|
858
|
+
====
|
859
|
+
[source,ruby]
|
860
|
+
----
|
861
|
+
class Example < Lutaml::Model::Serializable
|
862
|
+
attribute :name, Lutaml::Model::Type::String
|
863
|
+
attribute :value, Lutaml::Model::Type::Integer
|
864
|
+
|
865
|
+
json do
|
866
|
+
map 'name', to: :name
|
867
|
+
map 'value', to: :value
|
868
|
+
end
|
869
|
+
|
870
|
+
yaml do
|
871
|
+
map 'name', to: :name
|
872
|
+
map 'value', to: :value
|
873
|
+
end
|
874
|
+
|
875
|
+
toml do
|
876
|
+
map 'name', to: :name
|
877
|
+
map 'value', to: :value
|
878
|
+
end
|
879
|
+
end
|
880
|
+
----
|
881
|
+
|
882
|
+
[source,json]
|
883
|
+
----
|
884
|
+
{
|
885
|
+
"name": "John Doe",
|
886
|
+
"value": 28
|
887
|
+
}
|
888
|
+
----
|
889
|
+
|
890
|
+
[source,ruby]
|
891
|
+
----
|
892
|
+
> Example.from_json(json)
|
893
|
+
> #<Example:0x0000000104ac7240 @name="John Doe", @value=28>
|
894
|
+
> Example.new(name: "John Doe", value: 28).to_json
|
895
|
+
> #{"name"=>"John Doe", "value"=>28}
|
896
|
+
----
|
897
|
+
====
|
898
|
+
|
899
|
+
|
900
|
+
==== Nested attribute mappings
|
901
|
+
|
902
|
+
The `map` method can also be used to map nested key-value data models
|
903
|
+
by referring to a Lutaml::Model class as an attribute class.
|
904
|
+
|
905
|
+
[example]
|
906
|
+
====
|
907
|
+
[source,ruby]
|
908
|
+
----
|
909
|
+
class Glaze < Lutaml::Model::Serializable
|
910
|
+
attribute :color, Lutaml::Model::Type::String
|
911
|
+
attribute :temperature, Lutaml::Model::Type::Integer
|
912
|
+
|
913
|
+
json do
|
914
|
+
map 'color', to: :color
|
915
|
+
map 'temperature', to: :temperature
|
916
|
+
end
|
917
|
+
end
|
918
|
+
|
919
|
+
class Ceramic < Lutaml::Model::Serializable
|
920
|
+
attribute :type, Lutaml::Model::Type::String
|
921
|
+
attribute :glaze, Glaze
|
922
|
+
|
923
|
+
json do
|
924
|
+
map 'type', to: :type
|
925
|
+
map 'glaze', to: :glaze
|
926
|
+
end
|
927
|
+
end
|
928
|
+
----
|
929
|
+
|
930
|
+
[source,json]
|
931
|
+
----
|
932
|
+
{
|
933
|
+
"type": "Porcelain",
|
934
|
+
"glaze": {
|
935
|
+
"color": "Clear",
|
936
|
+
"temperature": 1050
|
937
|
+
}
|
938
|
+
}
|
939
|
+
----
|
940
|
+
|
941
|
+
[source,ruby]
|
942
|
+
----
|
943
|
+
> Ceramic.from_json(json)
|
944
|
+
> #<Ceramic:0x0000000104ac7240 @type="Porcelain", @glaze=#<Glaze:0x0000000104ac7240 @color="Clear", @temperature=1050>>
|
945
|
+
> Ceramic.new(type: "Porcelain", glaze: Glaze.new(color: "Clear", temperature: 1050)).to_json
|
946
|
+
> #{"type"=>"Porcelain", "glaze"=>{"color"=>"Clear", "temperature"=>1050}}
|
947
|
+
----
|
948
|
+
====
|
949
|
+
|
950
|
+
=== Advanced attribute mapping
|
951
|
+
|
952
|
+
==== Attribute mapping delegation
|
953
|
+
|
954
|
+
Delegate attribute mappings to nested objects using the `delegate` option.
|
955
|
+
|
956
|
+
Syntax:
|
957
|
+
|
958
|
+
[source,ruby]
|
959
|
+
----
|
960
|
+
xml | json | yaml | toml do
|
961
|
+
map 'key_value_model_attribute_name', to: :name_of_attribute, delegate: :model_to_delegate_to
|
962
|
+
end
|
963
|
+
----
|
964
|
+
|
965
|
+
.Using the `delegate` option to map attributes to nested objects
|
966
|
+
[example]
|
967
|
+
====
|
968
|
+
The following class will parse the JSON snippet below:
|
969
|
+
|
970
|
+
[source,ruby]
|
971
|
+
----
|
972
|
+
class Glaze < Lutaml::Model::Serializable
|
973
|
+
attribute :color, Lutaml::Model::Type::String
|
974
|
+
attribute :temperature, Lutaml::Model::Type::Integer
|
975
|
+
|
976
|
+
json do
|
977
|
+
map 'color', to: :color
|
978
|
+
map 'temperature', to: :temperature
|
979
|
+
end
|
980
|
+
end
|
981
|
+
|
982
|
+
class Ceramic < Lutaml::Model::Serializable
|
983
|
+
attribute :type, Lutaml::Model::Type::String
|
984
|
+
attribute :glaze, Glaze
|
985
|
+
|
986
|
+
json do
|
987
|
+
map 'type', to: :type
|
988
|
+
map 'color', to: :color, delegate: :glaze
|
989
|
+
end
|
990
|
+
end
|
991
|
+
----
|
992
|
+
|
993
|
+
[source,json]
|
994
|
+
----
|
995
|
+
{
|
996
|
+
"type": "Porcelain",
|
997
|
+
"color": "Clear"
|
998
|
+
}
|
999
|
+
----
|
1000
|
+
|
1001
|
+
[source,ruby]
|
1002
|
+
----
|
1003
|
+
> Ceramic.from_json(json)
|
1004
|
+
> #<Ceramic:0x0000000104ac7240 @type="Porcelain", @glaze=#<Glaze:0x0000000104ac7240 @color="Clear", @temperature=nil>>
|
1005
|
+
> Ceramic.new(type: "Porcelain", glaze: Glaze.new(color: "Clear")).to_json
|
1006
|
+
> #{"type"=>"Porcelain", "color"=>"Clear"}
|
1007
|
+
----
|
1008
|
+
====
|
1009
|
+
|
1010
|
+
|
1011
|
+
==== Attribute serialization with custom methods
|
1012
|
+
|
1013
|
+
Define custom methods for specific attribute mappings using the `with:` key for
|
1014
|
+
each serialization mapping block for `from` and `to`.
|
1015
|
+
|
1016
|
+
Syntax:
|
1017
|
+
|
1018
|
+
[source,ruby]
|
1019
|
+
----
|
1020
|
+
xml | json | yaml | toml do
|
1021
|
+
map 'key_value_model_attribute_name', to: :name_of_attribute, with: {
|
1022
|
+
to: :method_name_to_serialize,
|
1023
|
+
from: :method_name_to_deserialize
|
1024
|
+
}
|
1025
|
+
end
|
1026
|
+
----
|
1027
|
+
|
1028
|
+
.Using the `with:` key to define custom serialization methods
|
1029
|
+
[example]
|
1030
|
+
====
|
1031
|
+
The following class will parse the JSON snippet below:
|
1032
|
+
|
1033
|
+
[source,ruby]
|
1034
|
+
----
|
1035
|
+
class CustomCeramic < Lutaml::Model::Serializable
|
1036
|
+
attribute :name, Lutaml::Model::Type::String
|
1037
|
+
attribute :size, Lutaml::Model::Type::Integer
|
1038
|
+
|
1039
|
+
json do
|
1040
|
+
map 'name', to: :name, with: { to: :name_to_json, from: :name_from_json }
|
1041
|
+
map 'size', to: :size
|
1042
|
+
end
|
1043
|
+
|
1044
|
+
def name_to_json(model, value)
|
1045
|
+
"Masterpiece: #{value}"
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
def name_from_json(model, doc)
|
1049
|
+
doc['name'].sub(/^Masterpiece: /, '')
|
1050
|
+
end
|
1051
|
+
end
|
1052
|
+
----
|
1053
|
+
|
1054
|
+
[source,json]
|
1055
|
+
----
|
1056
|
+
{
|
1057
|
+
"name": "Masterpiece: Vase",
|
1058
|
+
"size": 12
|
1059
|
+
}
|
1060
|
+
----
|
1061
|
+
|
1062
|
+
[source,ruby]
|
1063
|
+
----
|
1064
|
+
> CustomCeramic.from_json(json)
|
1065
|
+
> #<CustomCeramic:0x0000000104ac7240 @name="Vase", @size=12>
|
1066
|
+
> CustomCeramic.new(name: "Vase", size: 12).to_json
|
1067
|
+
> #{"name"=>"Masterpiece: Vase", "size"=>12}
|
1068
|
+
----
|
1069
|
+
====
|
1070
|
+
|
1071
|
+
|
1072
|
+
|
1073
|
+
==== Attribute extraction
|
1074
|
+
|
1075
|
+
NOTE: This feature is for key-value data model serialization only.
|
1076
|
+
|
1077
|
+
The `child_mappings` option is used to extract results from a key-value data
|
1078
|
+
model (JSON, YAML, TOML) into a `Lutaml::Model` collection.
|
1079
|
+
|
1080
|
+
The values are extracted from the key-value data model using the list of keys
|
1081
|
+
provided.
|
1082
|
+
|
1083
|
+
Syntax:
|
1084
|
+
|
1085
|
+
[source,ruby]
|
1086
|
+
----
|
1087
|
+
json | yaml | toml do
|
1088
|
+
map 'key_value_model_attribute_name', to: :name_of_attribute,
|
1089
|
+
child_mappings: {
|
1090
|
+
key_attribute_name_1: <1>
|
1091
|
+
{path_to_value_1}, <2>
|
1092
|
+
key_attribute_name_2:
|
1093
|
+
{path_to_value_2},
|
1094
|
+
# ...
|
1095
|
+
}
|
1096
|
+
end
|
1097
|
+
----
|
1098
|
+
<1> The `key_attribute_name_1` is the attribute name in the model. The value of
|
1099
|
+
this attribute will be assigned the key of the hash in the key-value data model.
|
1100
|
+
|
1101
|
+
<2> The `path_to_value_1` is an array of keys that represent the path to the
|
1102
|
+
value in the key-value data model. The keys are used to extract the value from
|
1103
|
+
the key-value data model and assign it to the attribute in the model.
|
1104
|
+
|
1105
|
+
The `path_to_value` is in a nested array format with each value a symbol, where
|
1106
|
+
each symbol represents a key to traverse down. The last key in the path is the
|
1107
|
+
value to be extracted.
|
1108
|
+
|
1109
|
+
.Determining the path to value in a key-value data model
|
1110
|
+
[example]
|
1111
|
+
====
|
1112
|
+
The following JSON contains 2 keys in schema named `engine` and `gearbox`.
|
1113
|
+
|
1114
|
+
[source,json]
|
1115
|
+
----
|
1116
|
+
{
|
1117
|
+
"components": {
|
1118
|
+
"engine": {
|
1119
|
+
"manufacturer": "Ford",
|
1120
|
+
"model": "V8"
|
1121
|
+
},
|
1122
|
+
"gearbox": {
|
1123
|
+
"manufacturer": "Toyota",
|
1124
|
+
"model": "4-speed"
|
1125
|
+
}
|
1126
|
+
}
|
1127
|
+
}
|
1128
|
+
----
|
1129
|
+
|
1130
|
+
The path to value for the `engine` schema is `[:components, :engine]` and for
|
1131
|
+
the `gearbox` schema is `[:components, :gearbox]`.
|
1132
|
+
====
|
1133
|
+
|
1134
|
+
In `path_to_value`, the `:key` and `:value` are reserved instructions used to
|
1135
|
+
assign the key or value of the serialization data respectively as the value to
|
1136
|
+
the attribute.
|
1137
|
+
|
1138
|
+
[example]
|
1139
|
+
====
|
1140
|
+
In the following JSON content, the `path_to_value` for the object keys named
|
1141
|
+
`engine` and `gearbox` will utilize the `:key` keyword to assign the key of the
|
1142
|
+
object as the value of a designated attribute.
|
1143
|
+
|
1144
|
+
[source,json]
|
1145
|
+
----
|
1146
|
+
{
|
1147
|
+
"components": {
|
1148
|
+
"engine": { /*...*/ },
|
1149
|
+
"gearbox": { /*...*/ }
|
1150
|
+
}
|
1151
|
+
}
|
1152
|
+
----
|
1153
|
+
====
|
1154
|
+
|
1155
|
+
If a specified value path is not found, the corresponding attribute in the model
|
1156
|
+
will be assigned a `nil` value.
|
1157
|
+
|
1158
|
+
.Attribute values set to `nil` when the `path_to_value` is not found
|
1159
|
+
[example]
|
1160
|
+
====
|
1161
|
+
In the following JSON content, the `path_to_value` of `[:extras, :sunroof]` and
|
1162
|
+
`[:extras, :drinks_cooler]` at the object `"gearbox"` would be set to `nil`.
|
1163
|
+
|
1164
|
+
[source,json]
|
1165
|
+
----
|
1166
|
+
{
|
1167
|
+
"components": {
|
1168
|
+
"engine": {
|
1169
|
+
"manufacturer": "Ford",
|
1170
|
+
"extras": {
|
1171
|
+
"sunroof": true,
|
1172
|
+
"drinks_cooler": true
|
1173
|
+
}
|
1174
|
+
},
|
1175
|
+
"gearbox": {
|
1176
|
+
"manufacturer": "Toyota"
|
1177
|
+
}
|
1178
|
+
}
|
1179
|
+
}
|
1180
|
+
----
|
1181
|
+
====
|
1182
|
+
|
1183
|
+
|
1184
|
+
.Using the `child_mappings` option to extract values from a key-value data model
|
1185
|
+
[example]
|
1186
|
+
====
|
1187
|
+
The following JSON contains 2 keys in schema named `foo` and `bar`.
|
1188
|
+
|
1189
|
+
[source,json]
|
1190
|
+
----
|
1191
|
+
{
|
1192
|
+
"schemas": {
|
1193
|
+
"foo": { <1>
|
1194
|
+
"path": { <2>
|
1195
|
+
"link": "link one",
|
1196
|
+
"name": "one"
|
1197
|
+
}
|
1198
|
+
},
|
1199
|
+
"bar": { <1>
|
1200
|
+
"path": { <2>
|
1201
|
+
"link": "link two",
|
1202
|
+
"name": "two"
|
1203
|
+
}
|
1204
|
+
}
|
1205
|
+
}
|
1206
|
+
}
|
1207
|
+
----
|
1208
|
+
<1> The keys `foo` and `bar` are to be mapped to the `id` attribute.
|
1209
|
+
<2> The nested `path.link` and `path.name` keys are used as the `link` and
|
1210
|
+
`name` attributes, respectively.
|
1211
|
+
|
1212
|
+
A model can be defined for this JSON as follows:
|
1213
|
+
|
1214
|
+
[source,ruby]
|
1215
|
+
----
|
1216
|
+
class Schema < Lutaml::Model::Serializable
|
1217
|
+
attribute :id, Lutaml::Model::Type::String
|
1218
|
+
attribute :link, Lutaml::Model::Type::String
|
1219
|
+
attribute :name, Lutaml::Model::Type::String
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
class ChildMappingClass < Lutaml::Model::Serializable
|
1223
|
+
attribute :schemas, Schema, collection: true
|
1224
|
+
|
1225
|
+
json do
|
1226
|
+
map "schemas", to: :schemas,
|
1227
|
+
child_mappings: {
|
1228
|
+
id: :key,
|
1229
|
+
link: %i[path link],
|
1230
|
+
name: %i[path name],
|
1231
|
+
}
|
1232
|
+
end
|
1233
|
+
end
|
1234
|
+
----
|
1235
|
+
|
1236
|
+
The output becomes:
|
1237
|
+
|
1238
|
+
[source,ruby]
|
1239
|
+
----
|
1240
|
+
> ChildMappingClass.from_json(json)
|
1241
|
+
> #<ChildMappingClass:0x0000000104ac7240
|
1242
|
+
@schemas=
|
1243
|
+
[#<Schema:0x0000000104ac6e30 @id="foo", @link="link one", @name="one">,
|
1244
|
+
#<Schema:0x0000000104ac58f0 @id="bar", @link="link two", @name="two">]>
|
1245
|
+
> ChildMappingClass.new(schemas: [Schema.new(id: "foo", link: "link one", name: "one"), Schema.new(id: "bar", link: "link two", name: "two")]).to_json
|
1246
|
+
> #{"schemas"=>{"foo"=>{"path"=>{"link"=>"link one", "name"=>"one"}}, {"bar"=>{"path"=>{"link"=>"link two", "name"=>"two"}}}}
|
1247
|
+
----
|
1248
|
+
|
1249
|
+
In this example:
|
1250
|
+
|
1251
|
+
* The `key` of each schema (`foo` and `bar`) is mapped to the `id` attribute.
|
1252
|
+
|
1253
|
+
* The nested `path.link` and `path.name` keys are mapped to the `link` and
|
1254
|
+
`name` attributes, respectively.
|
1255
|
+
====
|
1256
|
+
|
1257
|
+
|
400
1258
|
== Adapters
|
401
1259
|
|
402
|
-
|
1260
|
+
=== General
|
1261
|
+
|
1262
|
+
Lutaml::Model uses an adapter pattern to support multiple libraries for each
|
1263
|
+
serialization format.
|
403
1264
|
|
404
|
-
|
1265
|
+
You will need to specify the configuration for the adapter you want to use. The
|
1266
|
+
easiest way is to copy and paste the following configuration into your code.
|
1267
|
+
|
1268
|
+
The default configuration is as follows:
|
405
1269
|
|
406
1270
|
[source,ruby]
|
407
1271
|
----
|
408
1272
|
require 'lutaml/model'
|
409
1273
|
require 'lutaml/model/xml_adapter/nokogiri_adapter'
|
410
|
-
require 'lutaml/model/
|
411
|
-
require 'lutaml/model/
|
1274
|
+
require 'lutaml/model/json_adapter/standard_json_adapter'
|
1275
|
+
require 'lutaml/model/toml_adapter/toml_rb_adapter'
|
1276
|
+
require 'lutaml/model/yaml_adapter/standard_yaml_adapter'
|
412
1277
|
|
413
1278
|
Lutaml::Model::Config.configure do |config|
|
414
1279
|
config.xml_adapter = Lutaml::Model::XmlAdapter::NokogiriAdapter
|
415
|
-
|
1280
|
+
config.yaml_adapter = Lutaml::Model::YamlAdapter::StandardYamlAdapter
|
1281
|
+
config.json_adapter = Lutaml::Model::JsonAdapter::StandardJsonAdapter
|
1282
|
+
config.toml_adapter = Lutaml::Model::TomlAdapter::TomlRbAdapter
|
416
1283
|
end
|
417
1284
|
----
|
418
1285
|
|
419
|
-
=== JSON: `JSON` and `MultiJson`
|
420
1286
|
|
1287
|
+
=== XML
|
1288
|
+
|
1289
|
+
Lutaml::Model supports the following XML adapters:
|
1290
|
+
|
1291
|
+
* Nokogiri (default)
|
1292
|
+
* Oga (optional, plain Ruby suitable for Opal/JS)
|
1293
|
+
* Ox (optional)
|
1294
|
+
|
1295
|
+
.Using the Nokogiri XML adapter
|
421
1296
|
[source,ruby]
|
422
1297
|
----
|
423
1298
|
require 'lutaml/model'
|
424
|
-
require 'lutaml/model/json_adapter/standard'
|
425
|
-
require 'lutaml/model/json_adapter/multi_json'
|
426
1299
|
|
427
1300
|
Lutaml::Model::Config.configure do |config|
|
428
|
-
|
429
|
-
|
1301
|
+
require 'lutaml/model/xml_adapter/nokogiri_adapter'
|
1302
|
+
config.xml_adapter = Lutaml::Model::XmlAdapter::NokogiriAdapter
|
430
1303
|
end
|
431
1304
|
----
|
432
1305
|
|
433
|
-
|
1306
|
+
.Using the Oga XML adapter
|
1307
|
+
[source,ruby]
|
1308
|
+
----
|
1309
|
+
require 'lutaml/model'
|
1310
|
+
|
1311
|
+
Lutaml::Model::Config.configure do |config|
|
1312
|
+
require 'lutaml/model/xml_adapter/oga_adapter'
|
1313
|
+
config.xml_adapter = Lutaml::Model::XmlAdapter::OgaAdapter
|
1314
|
+
end
|
1315
|
+
----
|
434
1316
|
|
1317
|
+
.Using the Ox XML adapter
|
1318
|
+
[source,ruby]
|
1319
|
+
----
|
1320
|
+
require 'lutaml/model'
|
1321
|
+
|
1322
|
+
Lutaml::Model::Config.configure do |config|
|
1323
|
+
require 'lutaml/model/xml_adapter/ox_adapter'
|
1324
|
+
config.xml_adapter = Lutaml::Model::XmlAdapter::OxAdapter
|
1325
|
+
end
|
1326
|
+
----
|
1327
|
+
|
1328
|
+
|
1329
|
+
=== YAML
|
1330
|
+
|
1331
|
+
Lutaml::Model supports only one YAML adapter.
|
1332
|
+
|
1333
|
+
* YAML (default)
|
1334
|
+
|
1335
|
+
.Using the YAML adapter
|
1336
|
+
[source,ruby]
|
1337
|
+
----
|
1338
|
+
require 'lutaml/model'
|
1339
|
+
|
1340
|
+
Lutaml::Model::Config.configure do |config|
|
1341
|
+
require 'lutaml/model/yaml_adapter/standard_yaml_adapter'
|
1342
|
+
config.yaml_adapter = Lutaml::Model::YamlAdapter::StandardYamlAdapter
|
1343
|
+
end
|
1344
|
+
----
|
1345
|
+
|
1346
|
+
|
1347
|
+
|
1348
|
+
=== JSON
|
1349
|
+
|
1350
|
+
Lutaml::Model supports the following JSON adapters:
|
1351
|
+
|
1352
|
+
* JSON (default)
|
1353
|
+
* MultiJson (optional)
|
1354
|
+
|
1355
|
+
.Using the JSON adapter
|
1356
|
+
[source,ruby]
|
1357
|
+
----
|
1358
|
+
require 'lutaml/model'
|
1359
|
+
|
1360
|
+
Lutaml::Model::Config.configure do |config|
|
1361
|
+
require 'lutaml/model/json_adapter/standard_json_adapter'
|
1362
|
+
config.json_adapter = Lutaml::Model::JsonAdapter::StandardJsonAdapter
|
1363
|
+
end
|
1364
|
+
----
|
1365
|
+
|
1366
|
+
.Using the MultiJson adapter
|
1367
|
+
[source,ruby]
|
1368
|
+
----
|
1369
|
+
require 'lutaml/model'
|
1370
|
+
|
1371
|
+
Lutaml::Model::Config.configure do |config|
|
1372
|
+
require 'lutaml/model/json_adapter/multi_json_adapter'
|
1373
|
+
config.json_adapter = Lutaml::Model::JsonAdapter::MultiJsonAdapter
|
1374
|
+
end
|
1375
|
+
----
|
1376
|
+
|
1377
|
+
=== TOML
|
1378
|
+
|
1379
|
+
Lutaml::Model supports the following TOML adapters:
|
1380
|
+
|
1381
|
+
* Toml-rb (default)
|
1382
|
+
* Tomlib (optional)
|
1383
|
+
|
1384
|
+
.Using the Toml-rb adapter
|
1385
|
+
[source,ruby]
|
1386
|
+
----
|
1387
|
+
require 'lutaml/model'
|
1388
|
+
|
1389
|
+
Lutaml::Model::Config.configure do |config|
|
1390
|
+
require 'lutaml/model/toml_adapter/toml_rb_adapter'
|
1391
|
+
config.toml_adapter = Lutaml::Model::TomlAdapter::TomlRbAdapter
|
1392
|
+
end
|
1393
|
+
----
|
1394
|
+
|
1395
|
+
.Using the Tomlib adapter
|
435
1396
|
[source,ruby]
|
436
1397
|
----
|
437
1398
|
require 'lutaml/model'
|
438
|
-
require 'lutaml/model/toml_adapter/toml_rb_adapter'
|
439
|
-
require 'lutaml/model/toml_adapter/tomlib_adapter'
|
440
1399
|
|
441
1400
|
Lutaml::Model::Config.configure do |config|
|
442
|
-
config.toml_adapter = Lutaml::Model::TomlAdapter::
|
443
|
-
|
1401
|
+
config.toml_adapter = Lutaml::Model::TomlAdapter::TomlibAdapter
|
1402
|
+
require 'lutaml/model/toml_adapter/tomlib_adapter'
|
444
1403
|
end
|
445
1404
|
----
|
446
1405
|
|
447
1406
|
== License and Copyright
|
448
1407
|
|
449
|
-
This project is licensed under the BSD 2-clause License
|
1408
|
+
This project is licensed under the BSD 2-clause License.
|
1409
|
+
See the LICENSE file for details.
|
450
1410
|
|
451
|
-
|
1411
|
+
Copyright Ribose.
|