General
Collections are used to represent a contained group of multiple instances of models.
Typically, a collection represents an "Array" or a "Set" in information modeling and programming languages. In LutaML, a collection represents an array of model instances.
Models in a collection may be:
-
constrained to be of a single kind;
-
constrained to be of multiple kinds sharing common characteristics;
-
unbounded of any kind.
LutaML Model provides the Lutaml::Model::Collection class for defining collections of model instances.
Configuration
All formats
The instances directive defined at the Collection class level is used to define the collection attribute and the model type of the collection elements.
Syntax:
class MyCollection < Lutaml::Model::Collection
instances {attribute}, {ModelType}
endWhere,
attribute-
The name of the attribute that contains the collection.
ModelType-
The model type of the collection elements.
Mapping instances: key-value formats only
The map_instances directive is only used in the key_value block.
Syntax:
class MyCollection < Lutaml::Model::Collection
instances {attribute}, ModelType
key_value do
map_instances to: {attribute}
end
endWhere,
attribute-
The name of the attribute that contains model instances.
This directive maps individual array elements to the defined instances attribute. These are the items considered part of the Collection and reflected as Enumerable elements.
Mapping instances: XML only
In the xml block, the map_element, map_attribute directives are used instead.
These directives map individual array elements to the defined instances attribute. These are the items considered part of the Collection and reflected as Enumerable elements.
Syntax for an element collection:
class MyCollection < Lutaml::Model::Collection
instances {attribute}, ModelType
xml do
map_element "element-name", to: {attribute}
end
endWhere,
element-name-
The name of the XML element of each model instance.
Syntax for an attribute collection:
class MyCollection < Lutaml::Model::Collection
instances {attribute}, ModelType
xml do
map_attribute "attribute-name", to: {attribute}
end
endWhere,
attribute-name-
The name of the XML attribute that contains all model instances.
XML attribute collections with delimited values
Lutaml::Model supports handling collections realized as XML attributes.
This feature allows you to serialize and deserialize multiple values stored in a single XML attribute, separated by a delimiter.
There are two approaches for handling delimited attribute values:
-
Using the
delimiter:option: provides simple splitting and joining with a fixed delimiter; -
Using the
as_list:option: provides custom import and export logic.
The delimiter option is a straightforward way to handle attribute values that are delimited strings. It is ideal for simple cases where you need basic string splitting and joining with a fixed delimiter. It automatically splits the string during import and joins the array during export.
class TitleDelimiterCollection < Lutaml::Model::Collection
instances :items, :string
xml do
root "titles"
map_attribute "title", to: :items, delimiter: "; " (1)
end
end| 1 | The delimiter used to split and join the string values. |
The as_list option provides full control over how values are split during import and joined during export. This is useful when you need custom parsing logic. This allows for more complex transformations beyond simple splitting and joining that is achieved through using delimiters.
class TitleCollection < Lutaml::Model::Collection
instances :items, :string
xml do
root "titles"
map_attribute "title", to: :items, as_list: {
import: ->(str) { str.split("; ") }, (1)
export: ->(arr) { arr.join("; ") }, (2)
}
end
end| 1 | Custom logic to split the string into an array during import. |
| 2 | Custom logic to join the array into a string during export. |
delimiter and as_list options for XML attribute collectionsBoth approaches work with the same XML format:
<titles title="Title One; Title Two; Title Three"/># Both collections work identically for basic use cases
collection = TitleCollection.from_xml(xml)
collection.items # => ["Title One", "Title Two", "Title Three"]
collection.to_xml # => '<titles title="Title One; Title Two; Title Three"/>'
# Same result with delimiter option
delimiter_collection = TitleDelimiterCollection.from_xml(xml)
delimiter_collection.items # => ["Title One", "Title Two", "Title Three"]
delimiter_collection.to_xml # => '<titles title="Title One; Title Two; Title Three"/>'Collection types
A LutaML collections is used for a number of scenarios:
-
Root collections (for key-value formats)
-
Named collections
-
Keyed collections (for key-value formats)
-
Attribute collections
-
Nested collections
Root collections (key-value formats only)
General
TODO: This case needs to be fixed for JSON.
A root collection is a collection that is not contained within a parent collection.
Root collections only apply to key-value serialization formats. The XML format does not support root collections.
| The XML standard mandates the existence of a non-repeated "root element" in an XML document. This means that a valid XML document must have a root element, and all elements in an XML document must exist within the root. This is why an XML document cannot be a "root collection". |
| A root collection cannot be represented using a non-collection model. |
Root collections store multiple instances of the same model type at the root level. In other words, these are model instances that do not have a defined container at the level of the LutaML Model.
There are two kinds of root collections depending on the type of the instance value:
- "Root value collection"
-
the value is a "primitive type"
- "Root object collection"
-
the value is a "model instance"
Regardless of the type of root collection, the instance in a collection is always a LutaML model instance.
Root value collections
A root value collection is a collection that directly contains values of a primitive type.
---
- Item One
- Item Two
- Item Three[
"Item One",
"Item Two",
"Item Three"
]Syntax:
class MyCollection < Lutaml::Model::Collection
instances :items, ModelType
end
class ModelType < Lutaml::Model::Serializable
attribute :name, :string
endCode:
class Title < Lutaml::Model::Serializable
attribute :content, :string
end
class TitleCollection < Lutaml::Model::Collection
instances :titles, Title
key_value do
no_root # default
map_instances to: :titles
end
endData:
---
- Title One
- Title Two
- Title Three[
"Title One",
"Title Two",
"Title Three"
]Usage:
titles = TitleCollection.from_yaml(yaml_data)
titles.count
# => 3
titles.first.content
# => "Title One"Root object collections
A root object collection is a collection that directly contains model instances, each containing at least one serialized attribute.
name---
- name: Item One
- name: Item Two
- name: Item Three[
{"name": "Item One"},
{"name": "Item Two"},
{"name": "Item Three"}
]Code:
class Title < Lutaml::Model::Serializable
attribute :content, :string
end
class TitleCollection < Lutaml::Model::Collection
instances :titles, Title
key_value do
no_root # default
map_instances to: :titles
end
endData:
---
- content: Title One
- content: Title Two
- content: Title Three[
{"content": "Title One"},
{"content": "Title Two"},
{"content": "Title Three"}
]Usage:
titles = TitleCollection.from_yaml(yaml_data)
titles.count
# => 3
titles.first.content
# => "Title One"Named collections
General
Named collections are collections wrapped inside a name or a key. The "name" of the collection serves as the container root of its contained model instances.
The named collection setup applies to XML and key-value serialization formats.
In a named collection setup, the collection is defined as a Lutaml::Model::Collection class, and each instance is defined as a Lutaml::Model::Serializable class.
There are two kinds of named collections depending on the type of the instance value:
- "Named value collection"
-
the value is a "primitive type"
- "Named object collection"
-
the value is a "model instance"
Regardless of the name of root collection, the instance in a collection is always a LutaML model instance.
Named value collections
A named value collection is a collection that contains values of a primitive type.
<names>
<name>Item One</name>
<name>Item Two</name>
<name>Item Three</name>
</names>---
names:
- Item One
- Item Two
- Item ThreeSyntax:
class MyCollection < Lutaml::Model::Collection
instances :items, ModelType
xml do
root "name-of-xml-container-element"
end
key_value do
root "name-of-key-value-container-element"
end
end
class ModelType < Lutaml::Model::Serializable
attribute :name, :string
endA named collection can alternatively be implemented as a non-collection model ("Model class with an attribute") that contains the collection of instances. In this case, the attribute will be an Array object, which does not contain additional attributes and methods.
class Title < Lutaml::Model::Serializable
attribute :title, :string
xml do
root "title"
map_content to: :title
end
end
class DirectTitleCollection < Lutaml::Model::Collection
instances :items, Title
xml do
root "titles"
map_element "title", to: :items
end
key_value do
map_instances to: :items
end
end<titles>
<title>Title One</title>
<title>Title Two</title>
<title>Title Three</title>
</titles>---
titles:
- Title One
- Title Two
- Title Three{
"titles": [
"Title One",
"Title Two",
"Title Three"
]
}titles = DirectTitleCollection.from_yaml(yaml_data)
titles.count
# => 3
titles.first.title
# => "Title One"
titles.last.title
# => "Title Three"Named object collections
A named object collection is a collection that contains model instances, each containing at least one serialized attribute.
| A named object collection can alternatively be implemented as a non-collection model ("Model class with an attribute") that contains the collection of instances. In this case, the attribute will be an Array object, which does not contain additional attributes and methods. |
<names>
<name><content>Item One</content></name>
<name><content>Item Two</content></name>
<name><content>Item Three</content></name>
</names>---
names:
- name: Item One
- name: Item Two
- name: Item ThreeData:
<titles>
<title><content>Title One</content></title>
<title><content>Title Two</content></title>
<title><content>Title Three</content></title>
</titles>---
titles:
- title: Title One
- title: Title Two
- title: Title Three{
"titles": [
{"title": "Title One"},
{"title": "Title Two"},
{"title": "Title Three"}
]
}Code:
class Title < Lutaml::Model::Serializable
attribute :title, :string
xml do
root "title"
map_element "content", to: :title
end
key_value do
map "title", to: :title
end
end
class TitleCollection < Lutaml::Model::Collection
instances :items, Title
xml do
root "titles"
map_element 'title', to: :items
end
key_value do
root "titles"
map_instances to: :items
end
endUsage:
titles = TitleCollection.from_yaml(yaml_data)
titles.count
# => 3
titles.first.title
# => "Title One"
titles.last.title
# => "Title Three"Attribute collection class
A model attribute that is a collection can be contained within a custom collection class.
A custom collection class can be defined to provide custom behavior for the collection inside a non-collection model, with attributes using collection: true.
Syntax:
class MyModel < Lutaml::Model::Serializable
attribute {model-attribute}, ModelType, collection: MyCollection
end
class MyCollection < Lutaml::Model::Collection
instances {instance-name}, ModelType
# Custom behavior for the collection
def custom_method
# Custom logic here
end
endData:
<titles>
<title>Title One</title>
<title>Title Two</title>
<title>Title Three</title>
</titles>titles:
- title: Title One
- title: Title Two
- title: Title Threeclass StringParts < Lutaml::Model::Collection
instances :parts, :string
def to_s
parts.join(' -- ')
end
end
class BibliographicItem < Lutaml::Model::Serializable
attribute :title_parts, :string, collection: StringParts
xml do
root "titles"
map_element "title", to: :title_parts
end
key_value do
root "titles"
map_instances to: :title_parts
end
def render_title
title_parts.to_s
end
end> bib_item = BibliographicItem.from_xml(xml_data)
> bib_item.title_parts
> # StringParts:0x0000000104ac7240 @parts=["Title One", "Title Two", "Title Three"]
> bib_item.render_title
> # "Title One -- Title Two -- Title Three"Nested collections
TODO: This case needs to be fixed.
Collections can be nested within other models and define their own serialization rules.
Nested collections can be defined in the same way as root collections, but they are defined within the context of a parent model.
Data:
<titles>
<title-group>
<artifact>
<content>Title One</content>
</artifact>
<artifact>
<content>Title Two</content>
</artifact>
<artifact>
<content>Title Three</content>
</artifact>
</title-group>
</titles>---
titles:
title-group:
- artifact:
content: Title One
- artifact:
content: Title Two
- artifact:
content: Title Threeclass Title < Lutaml::Model::Serializable
attribute :content, :string
end
class TitleCollection < Lutaml::Model::Collection
instances :items, Title
xml do
root "title-group"
map_element "artifact", to: :items
end
end
class BibItem < Lutaml::Model::Serializable
attribute :titles, TitleCollection
xml do
root "bibitem"
# This overrides the collection's root "title-group"
map_element "titles", to: :titles
end
endKeyed collections (key-value serialization formats only)
General
In key-value serialization formats, a key can be used to uniquely identify each instance. This usage allows for enforcing uniqueness in the collection.
A collection that contains keyed objects as its instances is commonly called a "keyed collection". A keyed object in a serialization format is an object identified with a unique key.
| The concept of keyed collections does not typically apply to XML collections. |
There are two kinds of keyed collections depending on the type of the keyed value:
- "keyed value collection"
-
the value is a "primitive type"
- "keyed object collection"
-
the value is a "model instance"
Regardless of the type of keyed collections, the instance in a collection is always a LutaML model instance.
map_key method
The map_key method specifies that the unique key is to be moved into an attribute belonging to the instance model.
Syntax:
key_value do
map_key to_instance: {instance-attribute-name}
endWhere,
to_instance-
Refers to the attribute name in the instance that contains the key.
{key_attribute}-
The attribute name in the instance that contains the key.
map_value method
The map_value method specifies that the value (the object referenced by the unique key) is to be moved into an attribute belonging to the instance model.
Syntax:
key_value do
# basic pattern
map_value {operation}: [*argument]
# Mapping the value object to a full instance through `to_instance`
map_value to_instance: {instance-attribute-name}
# Mapping the value object to an attribute as_instance
map_value as_attribute: {instance-attribute-name}
endKeyed value collections
A keyed value collection is a collection where the keyed item in the serialization format is a primitive type (e.g. string, integer, etc.).
The instance item inside the collection is a model instance that contains both the serialized key and serialized value both as attributes inside the model.
All three map_key, map_value, and map_instances methods need to be used to define how instances are mapped in a keyed value collection.
class AuthorAvailability < Lutaml::Model::Serializable
attribute :id, :string
attribute :available, :boolean
end
class AuthorCollection < Lutaml::Model::Collection
instances :authors, AuthorAvailability
key_value do
map_key to_instance: :id # This refers to 'authors[].id'
map_value as_attribute: :available # This refers to 'authors[].available'
map_instances to: :authors
end
end---
author_01: true
author_02: false
author_03: trueauthors = AuthorCollection.from_yaml(yaml_data)
authors.first.id
# => "author_01"
authors.first.available
# => trueKeyed object collections
A keyed object collection is a collection where the keyed item in the serialization format contains multiple attributes.
The instance item inside the collection is a model instance that contains the serialized key as one attribute, and the serialized value attributes are all attributes inside the model.
Both the map_key and map_instances are used to define how instances are mapped in a keyed object collection.
class Author < Lutaml::Model::Serializable
attribute :id, :string
attribute :name, :string
end
class AuthorCollection < Lutaml::Model::Collection
instances :authors, Author
key_value do
map_key to_instance: :id # This refers to 'authors[].id'
map_instances to: :authors
end
end---
author_01:
name: Author One
author_02:
name: Author Two
author_03:
name: Author Threeauthors = AuthorCollection.from_yaml(yaml_data)
authors.first.id
# => "author_01"
authors.first.name
# => "Author One"Nested keyed object collection
A nested keyed object collection is a keyed collection that contain other keyed collections. This case is simply a more complex arrangement of the principles applied to keyed object collections.
This pattern can extend to multiple levels of nesting, where each level contains a keyed object collection that can have its own key and value mappings.
Depends on whether a custom collection class is needed, the following mechanisms are available:
-
When using a Lutaml::Model::Serializable class for a keyed collection, use the
child_mappingsoption to map attributes. -
When using a Lutaml::Model::Collection class for a keyed collection, there are two options:
-
use the
map_key,map_value, andmap_instancesmethods to map attributes; or -
use the
root_mappingsoption to map attributes.
This example provides a two-layer nested structure where:
-
The first layer keys pieces by type (
bowls,vases). -
The second layer keys glazes by finish name within each piece type.
-
Each glaze finish contains detailed attributes like temperature.
# Third layer represents glaze finishes.
class GlazeFinish < Lutaml::Model::Serializable
attribute :name, :string
attribute :temperature, :integer
key_value do
map "name", to: :name
map "temperature", to: :temperature
end
end
# Second layer represents ceramic pieces each with multiple finishes.
class CeramicPiece < Lutaml::Model::Serializable
attribute :piece_type, :string
attribute :glazes, GlazeFinish, collection: true
key_value do
map "piece_type", to: :piece_type
map "glazes", to: :glazes, child_mappings: {
name: :key,
temperature: :temperature
}
end
end
# Uppermost layer represents the collection of ceramic pieces.
class StudioInventory < Lutaml::Model::Collection
instances :pieces, CeramicPiece
key_value do
map to: :pieces, root_mappings: {
piece_type: :key,
glazes: :value,
}
end
end---
bowls:
matte_finish:
name: Earth Matte
temperature: 1240
glossy_finish:
name: Ocean Blue
temperature: 1260
crackle_finish:
name: Antique Crackle
temperature: 1220
vases:
metallic_finish:
name: Bronze Metallic
temperature: 1280
crystalline_finish:
name: Ice Crystal
temperature: 1300inventory = StudioInventory.from_yaml(yaml_data)
# Access nested data through the hierarchy
puts inventory.pieces.bowls.matte_finish.name
# => "Earth Matte"
puts inventory.pieces.bowls.matte_finish.temperature
# => 1240
# Iterate through all pieces and their glazes
inventory.pieces.each do |piece_type, piece|
puts "#{piece_type.capitalize}:"
piece.glazes.each do |glaze_name, glaze|
puts " #{glaze_name}: #{glaze.name} (#{glaze.temperature}°C)"
end
endBehavior
Enumerable interface
Collections implement the Ruby Enumerable interface, providing standard collection operations.
Collections allow the following sample Enumerable methods:
-
each- Iterate over collection items -
map- Transform collection items -
select- Filter collection items -
find- Find items matching criteria -
reduce- Aggregate collection items
# Filter items
filtered = collection.filter { |item| item.id == "1" }
# Reject items
rejected = collection.reject { |item| item.id == "1" }
# Select items
selected = collection.select { |item| item.id == "1" }
# Map items
mapped = collection.map { |item| item.name }
# Count items
count = collection.countInitialization
Collections can be initialized with an array of items or through individual item addition.
# Empty collection
collection = ItemCollection.new
# From an array of items
collection = ItemCollection.new([item1, item2, item3])
# From an array of hashes
collection = ItemCollection.new([
{ id: "1", name: "Item 1" },
{ id: "2", name: "Item 2" }
])
# Adding items later
collection << Item.new(id: "3", name: "Item 3")Collection mutation
Collection attributes can be mutated after initialization using standard Ruby array methods or custom helper methods. These mutations are properly reflected during serialization.
When a collection attribute is defined with a default value (e.g., default: → { [] }), you can mutate it directly using Ruby’s array methods such as <<, push, concat, etc., or through custom methods that add items to the collection.
class TabStop < Lutaml::Model::Serializable
attribute :position, :integer
attribute :alignment, :string
xml do
element 'tab'
map_attribute 'pos', to: :position
map_attribute 'val', to: :alignment
end
end
class TabStopCollection < Lutaml::Model::Serializable
attribute :tabs, TabStop, collection: true, default: -> { [] }
xml do
element 'tabs'
map_element 'tab', to: :tabs
end
# Custom helper method for adding tabs
def add_tab(position, alignment)
tabs << TabStop.new(position: position, alignment: alignment)
end
end
# Create collection with default empty array
collection = TabStopCollection.new
# Mutate using << operator
collection.tabs << TabStop.new(position: 1440, alignment: "center")
# Mutate using custom method
collection.add_tab(2880, "right")
# Mutations are properly serialized
puts collection.to_xml
# <tabs>
# <tab pos="1440" val="center"/>
# <tab pos="2880" val="right"/>
# </tabs>This behavior enables natural Ruby idioms for working with collections:
-
Direct mutation: Use
<<,push,pop,shift,unshift,concat, etc. -
Custom methods: Create domain-specific helper methods like
add_item,remove_item -
No recreation needed: No need to recreate parent objects to update collections
Empty collections initialized with default values are not serialized unless items are added. This preserves the expected behavior where default values are only rendered when explicitly requested with render_default: true. |
Ordering
TODO: This case needs to be fixed.
Collections that maintain a specific ordering of elements.
Syntax:
class MyCollection < Lutaml::Model::Collection
instances {instances-name}, ModelType
ordered by: {attribute-of-instance-or-proc}, order: {:asc | :desc}
endWhere,
{instances-name}-
name of the instances accessor within the collection
ModelType-
The model type of the collection elements.
{attribute-of-instance-or-proc}-
How model instances are to be ordered by. Values supported are:
{attribute-of-instance}-
Attribute name of an instance to be ordered by.
{proc}-
Proc that returns a value to order by (same as
sort_by), given the instance as input. order-
Order direction of the value:
:asc-
Ascending order (default).
:desc-
Descending order.
When a proc is provided for ordering and order: :desc is specified, the collection is first sorted using the proc (as with Ruby’s sort_by), and the resulting array is then reversed to achieve descending order. |
Data:
<items>
<item id="3" name="Item Three"/>
<item id="1" name="Item One"/>
<item id="2" name="Item Two"/>
</items>---
- id: 3
name: Item Three
- id: 1
name: Item One
- id: 2
name: Item Twoclass Item < Lutaml::Model::Serializable
attribute :id, :string
attribute :name, :string
xml do
map_attribute "id", to: :id
map_attribute "name", to: :name
end
end
class OrderedItemCollection < Lutaml::Model::Collection
instances :items, Item
ordered by: :id, order: :desc
xml do
root "items"
map_element "item", to: :items
end
key_value do
no_root
map_instances to: :items
end
end> collection = OrderedItemCollection.from_xml(xml_data)
> collection.map(&:id)
> # ["3", "2", "1"]
> collection = OrderedItemCollection.from_yaml(yaml_data)
> collection.map(&:id)
> # ["3", "2", "1"]class ProcOrderedItemCollection < Lutaml::Model::Collection
instances :items, Item
# Multi-level ordering: first by name length, then by name alphabetically
ordered by: ->(item) { [item.name.length, item.name] }, order: :asc
xml do
root "items"
map_element "item", to: :items
end
end> items_data = [
> { id: "1", name: "Zebra" },
> { id: "2", name: "Alpha" },
> { id: "3", name: "Beta" }
> ]
> complex_collection = ProcOrderedItemCollection.new(items_data)
> complex_collection.map(&:name)
> # ["Beta", "Alpha", "Zebra"] # Sorted by name length then alphabeticallyIndexed lookups
Collections can be indexed for O(1) lookups by one or more attributes. This is particularly useful for large collections where repeated lookups by key would otherwise be O(n).
Single index
Use index_by with a single field for simple indexed lookups:
class Person < Lutaml::Model::Serializable
attribute :id, :string
attribute :name, :string
end
class PersonCollection < Lutaml::Model::Collection
instances :people, Person
index_by :id
end
people = PersonCollection.new([
{ id: '001', name: 'Alice' },
{ id: '002', name: 'Bob' }
])
# O(1) lookup by id
people.fetch('001') # => #<Person id='001' name='Alice'>
people.find_by(:id, '002') # => #<Person id='002' name='Bob'>
people.find_by(:id, 'missing') # => nilMultiple indexes
Use index_by with multiple fields for lookups by different keys:
class PersonCollection < Lutaml::Model::Collection
instances :people, Person
index_by :id, :email, :slug
end
# O(1) lookup by any indexed field
people.find_by(:id, '001')
people.find_by(:email, 'alice@example.com')
people.find_by(:slug, 'alice-smith')Named indexes with custom key extraction
Use index with a name and proc for custom key extraction:
class PersonCollection < Lutaml::Model::Collection
instances :people, Person
index_by :id
index :email, by: ->(person) { person.email.downcase }
end
# Lookup with normalized key (proc stores lowercase)
people.find_by(:email, 'alice@example.com') # => finds AliceCombined with sorting
Indexes work alongside sorting:
class PersonCollection < Lutaml::Model::Collection
instances :people, Person
index_by :id
sort by: :name, order: :asc
end
# Collection is sorted by name, indexed by id
people.fetch('001') The index is rebuilt after any mutation (<<, push, []=). For best performance with large collections, batch mutations before lookups. |
Polymorphic collections
Collections can contain instances of different model classes that share a common base class.
The polymorphic option for instances allows you to specify which subclasses are accepted:
class ReferenceSet < Lutaml::Model::Collection
# Accepts any subclass of Reference
instances :references, Reference, polymorphic: true
end
class ReferenceSet < Lutaml::Model::Collection
# Accepts only DocumentReference and AnchorReference
instances :references, Reference, polymorphic: [
DocumentReference,
AnchorReference,
]
endPolymorphic collection mapping
To serialize/deserialize polymorphic collections, use the polymorphic option in your mapping blocks. This allows you to specify how to differentiate subclasses in XML, YAML, or JSON.
class ReferenceSet < Lutaml::Model::Collection
instances :references, Reference, polymorphic: [
DocumentReference,
AnchorReference,
]
xml do
root "ReferenceSet"
map_instances to: :references, polymorphic: {
attribute: "_class",
class_map: {
"document-ref" => "DocumentReference",
"anchor-ref" => "AnchorReference",
},
}
end
key_value do
map_instances to: :references, polymorphic: {
attribute: "_class",
class_map: {
"Document" => "DocumentReference",
"Anchor" => "AnchorReference",
},
}
end
endThis allows round-trip serialization like:
---
references:
- _class: Document
name: The Tibetan Book of the Dead
document_id: book:tbtd
- _class: Anchor
name: Chapter 1
anchor_id: book:tbtd:anchor-1<ReferenceSet>
<references reference-type="document-ref">
<name>The Tibetan Book of the Dead</name>
<document_id>book:tbtd</document_id>
</references>
<references reference-type="anchor-ref">
<name>Chapter 1</name>
<anchor_id>book:tbtd:anchor-1</anchor_id>
</references>
</ReferenceSet>Polymorphic mapping with subclass differentiator
If you cannot modify the polymorphic superclass, define the differentiator attribute in each subclass, and use the polymorphic mapping option in the collection:
class Reference < Lutaml::Model::Serializable
attribute :name, :string
end
class DocumentReference < Reference
attribute :_class, :string
attribute :document_id, :string
xml do
map_element "document_id", to: :document_id
map_attribute "reference-type", to: :_class
end
key_value do
map "document_id", to: :document_id
map "_class", to: :_class
end
end
class AnchorReference < Reference
attribute :_class, :string
attribute :anchor_id, :string
xml do
map_element "anchor_id", to: :anchor_id
map_attribute "reference-type", to: :_class
end
key_value do
map "anchor_id", to: :anchor_id
map "_class", to: :_class
end
end
class ReferenceSet < Lutaml::Model::Collection
instances :references, Reference, polymorphic: [
DocumentReference,
AnchorReference,
]
xml do
root "ReferenceSet"
map_instances to: :references, polymorphic: {
attribute: "_class",
class_map: {
"document-ref" => "DocumentReference",
"anchor-ref" => "AnchorReference",
},
}
end
key_value do
map_instances to: :references, polymorphic: {
attribute: "_class",
class_map: {
"Document" => "DocumentReference",
"Anchor" => "AnchorReference",
},
}
end
end