Overview
This guide helps you migrate from the old XML mapping approach to the new namespace-aware architecture introduced in Lutaml::Model.
Key changes
From root() to element()
Old approach:
class Ceramic < Lutaml::Model::Serializable
xml do
root 'ceramic'
map_element 'type', to: :type
end
endNew approach (recommended):
class Ceramic < Lutaml::Model::Serializable
xml do
element 'ceramic' # Modern API
map_element 'type', to: :type
end
end root() still works as a backward-compatible alias. No breaking changes. |
From no_root to type-only models
Old approach (deprecated):
class Address < Lutaml::Model::Serializable
xml do
no_root # DEPRECATED - shows warning
map_element 'street', to: :street
map_element 'city', to: :city
end
endNew approach (recommended):
class Address < Lutaml::Model::Serializable
xml do
# No element() or root() call - this is a type-only model
sequence do
map_element 'street', to: :street
map_element 'city', to: :city
end
end
endImportant: Type-only models can only be parsed when embedded in parent models, not standalone.
From string namespaces to XmlNamespace classes
Old approach (still works):
class Ceramic < Lutaml::Model::Serializable
xml do
root 'ceramic'
namespace 'http://example.com/ceramic', 'cer'
map_element 'type', to: :type
end
endNew approach (recommended):
class CeramicNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/ceramic'
prefix_default 'cer'
element_form_default :qualified
version '1.0'
documentation "Ceramic schema"
end
class Ceramic < Lutaml::Model::Serializable
xml do
element 'ceramic'
namespace CeramicNamespace
map_element 'type', to: :type
end
endBenefits: * Centralized namespace metadata * Reusable across models * Full XSD generation support * Qualification control * Version and documentation tracking
Migration steps
Step 1: Identify models to migrate
Find models using: * no_root - Needs migration to type-only pattern * String-based namespace() - Consider XmlNamespace classes for better organization
Step 2: Update type-only models
For each model with no_root:
-
Remove the
no_rootcall -
Ensure no
element()orroot()call exists -
Keep all mappings (
map_element, etc.) -
Consider wrapping in
sequenceif order matters
Step 3: Create XmlNamespace classes (optional)
For models with complex namespace requirements:
-
Create a namespace class
-
Define all metadata (URI, prefix, qualification, etc.)
-
Replace string-based
namespace()with class reference
Migration examples
Example 1: Simple no_root migration
Before:
class Address < Lutaml::Model::Serializable
attribute :street, :string
attribute :city, :string
xml do
no_root
map_element 'street', to: :street
map_element 'city', to: :city
end
endAfter:
class Address < Lutaml::Model::Serializable
attribute :street, :string
attribute :city, :string
xml do
# No element declaration needed
sequence do
map_element 'street', to: :street
map_element 'city', to: :city
end
end
endExample 2: Namespace class migration
Before:
class Contact < Lutaml::Model::Serializable
attribute :name, :string
xml do
root 'contact'
namespace 'https://example.com/contact/v1', 'contact'
map_element 'name', to: :name
end
end
class Person < Lutaml::Model::Serializable
attribute :full_name, :string
xml do
root 'person'
namespace 'https://example.com/contact/v1', 'contact'
map_element 'fullName', to: :full_name
end
endAfter:
# Define once, reuse everywhere
class ContactNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/contact/v1'
prefix_default 'contact'
element_form_default :qualified
version '1.0'
end
class Contact < Lutaml::Model::Serializable
attribute :name, :string
xml do
element 'contact'
namespace ContactNamespace # Reuse
map_element 'name', to: :name
end
end
class Person < Lutaml::Model::Serializable
attribute :full_name, :string
xml do
element 'person'
namespace ContactNamespace # Reuse
map_element 'fullName', to: :full_name
end
endExample 3: Complete migration
Before:
class Metadata < Lutaml::Model::Serializable
attribute :version, :string
xml do
no_root
map_attribute 'version', to: :version
end
end
class Document < Lutaml::Model::Serializable
attribute :title, :string
attribute :metadata, Metadata
xml do
root 'document'
namespace 'https://example.com/doc', 'doc'
map_element 'title', to: :title
map_element 'metadata', to: :metadata
end
endAfter:
class DocNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/doc'
prefix_default 'doc'
element_form_default :qualified
version '2.0'
documentation "Document schema v2"
end
# Type-only model
class Metadata < Lutaml::Model::Serializable
attribute :version, :string
xml do
# No element() - type-only
map_attribute 'version', to: :version
end
end
class Document < Lutaml::Model::Serializable
attribute :title, :string
attribute :metadata, Metadata
xml do
element 'document'
namespace DocNamespace
documentation "A document with metadata"
sequence do
map_element 'title', to: :title
map_element 'metadata', to: :metadata
end
end
endCompatibility notes
What still works
-
root()- Maintained as alias toelement() -
String-based
namespace(uri, prefix)- Still fully supported -
no_root- Works but shows deprecation warning -
All existing XML mappings - No breaking changes
What’s new (opt-in)
-
element()- Modern API for element declaration -
XmlNamespaceclasses - Centralized namespace metadata -
Type-only models via absence of element declaration
-
form:option - Per-element/attribute qualification control -
documentation:option - XSD annotation support -
Enhanced XSD generation with full namespace support
Incremental migration strategy
You don’t need to migrate everything at once. Consider this approach:
-
Phase 1: Update
no_rootmodels to type-only pattern-
Low risk, high clarity improvement
-
Can be done model-by-model
-
-
Phase 2: Replace
root()withelement()in new code-
No urgency, just style preference
-
Makes intent clearer
-
-
Phase 3: Introduce XmlNamespace classes where beneficial
-
For projects with multiple models in same namespace
-
When generating XSD
-
When documentation is important
-
-
Phase 4: Add metadata as needed
-
documentationfor XSD generation -
type_namefor custom type names -
form:for qualification control
-
Testing after migration
Ensure your migrated code works correctly:
# Round-trip test
original = YourModel.new(attr: "value")
xml = original.to_xml
parsed = YourModel.from_xml(xml)
assert_equal original, parsed
# Namespace test
assert_includes xml, 'xmlns:prefix="namespace-uri"'
# XSD generation test (if using)
xsd = Lutaml::Model::Schema.to_xml(YourModel)
assert_includes xsd, '<xs:schema'
assert_includes xsd, 'targetNamespace'Deprecated syntax migration
Type::Value namespace directive
Why the change
-
XML-specific clarity: Clearly indicates this is XML serialization configuration
-
Future compatibility: Reserves
namespacefor semantic namespace features (JSON-LD@context, RDF IRIs) -
Consistency: Maintains consistency with format-specific configuration patterns
-
Disambiguation: Distinguishes Type-level namespace from Model-level
namespacedirective
Migration steps
-
Identify affected classes
Find all
Type::Valuesubclasses usingnamespace:# Search for patterns like: class CustomType < Lutaml::Model::Type::String namespace SomeNamespace # ⚠️ DEPRECATED end -
Replace with xml_namespace
class CustomType < Lutaml::Model::Type::String xml_namespace SomeNamespace # ✅ CURRENT end -
No other changes needed
Functionality remains identical, serialization and deserialization work the same
Backward compatibility
The deprecated namespace directive:
-
✅ Continues to work in current versions
-
⚠️ Shows deprecation warning:
[DEPRECATION] Type::Value.namespace is deprecated. Use xml_namespace instead. This will be removed in version 1.0.0 -
❌ Will be removed in Lutaml::Model version 1.0.0
Complete example
class DublinCoreNamespace < Lutaml::Model::XmlNamespace
uri 'http://purl.org/dc/elements/1.1/'
prefix_default 'dc'
end
class DcTitleType < Lutaml::Model::Type::String
namespace DublinCoreNamespace # ⚠️ DEPRECATED
xsd_type 'titleType'
def self.cast(value)
super(value)
end
end
class Document < Lutaml::Model::Serializable
attribute :title, DcTitleType
xml do
root 'document'
map_element 'title', to: :title
end
endclass DublinCoreNamespace < Lutaml::Model::XmlNamespace
uri 'http://purl.org/dc/elements/1.1/'
prefix_default 'dc'
end
class DcTitleType < Lutaml::Model::Type::String
xml_namespace DublinCoreNamespace # ✅ CURRENT
xsd_type 'titleType'
def self.cast(value)
super(value)
end
end
class Document < Lutaml::Model::Serializable
attribute :title, DcTitleType
xml do
root 'document'
map_element 'title', to: :title
end
endResult: Both versions produce identical XML output and parse correctly, but the new syntax is future-proof and won’t trigger deprecation warnings.