Introduction
Type-level namespaces allow Lutaml::Model::Type::Value classes to declare their XML namespace using the unified xml do … end block. This enables:
-
Reusable types that belong to specific namespaces (e.g., Dublin Core properties, custom XSD types)
-
Multi-namespace documents where different types use different namespaces
-
Consistent namespace handling across all uses of the type
-
W3C-compliant round-trip serialization and deserialization
Type namespaces are particularly useful when working with standard vocabularies like Dublin Core, where properties have well-defined namespaces that should be consistent regardless of where they’re used.
Basic Usage
Declaring a type namespace (unified API)
Use the xml do … end block in your custom type class with the namespace directive:
class DcTitleType < Lutaml::Model::Type::String
xml do
namespace DublinCoreNamespace
end
endWhere DublinCoreNamespace is an XmlNamespace class:
class DublinCoreNamespace < Lutaml::Model::XmlNamespace
uri "http://purl.org/dc/elements/1.1/"
prefix_default "dc"
end The unified xml do … end block API is consistent with Model classes. The old xml_namespace directive at class level is deprecated but still supported. |
Using types with namespaces
Once declared, use the type in model attributes as normal:
class Article < Lutaml::Model::Serializable
attribute :title, DcTitleType
attribute :creator, DcCreatorType
xml do
root "article"
namespace ArticleNamespace
map_element "title", to: :title
map_element "creator", to: :creator
end
endThe type’s namespace is automatically applied during serialization.
Serialization Behavior
Default: Local declaration with prefix
Type namespaces automatically use prefix format with local declaration to avoid conflicting with the element’s default namespace.
class ArticleNamespace < Lutaml::Model::XmlNamespace
uri "http://example.com/article"
prefix_default "art"
end
class DcTitleType < Lutaml::Model::Type::String
xml do
namespace DublinCoreNamespace
end
end
class Article < Lutaml::Model::Serializable
attribute :title, DcTitleType
xml do
root "article"
namespace ArticleNamespace
map_element "title", to: :title
end
end
article = Article.new(title: "Introduction to Pottery")
puts article.to_xmlOutput:
<article xmlns="http://example.com/article">
<dc:title xmlns:dc="http://purl.org/dc/elements/1.1/">Introduction to Pottery</dc:title>
</article>Note: - Article uses default namespace format (xmlns="…") - Title uses prefix format with local declaration (xmlns:dc="…" on the element)
Why prefix format?
Type namespaces use prefix format (not default format) because:
-
Avoid conflicts: If the element uses default namespace, type cannot also use default
-
W3C compliance: Multiple namespaces require prefixes to distinguish them
-
Clarity: Explicit prefix shows which namespace each element belongs to
Integration with namespace_scope
Hoisting type namespaces
Use namespace_scope to declare type namespaces at the root element:
class Article < Lutaml::Model::Serializable
attribute :title, DcTitleType
attribute :creator, DcCreatorType
xml do
root "article"
namespace ArticleNamespace
namespace_scope [ArticleNamespace, DublinCoreNamespace] (1)
map_element "title", to: :title
map_element "creator", to: :creator
end
end
article = Article.new(
title: "Introduction to Pottery",
creator: "Jane Smith"
)
puts article.to_xml| 1 | Include DublinCoreNamespace in namespace_scope to hoist it |
Output:
<article xmlns="http://example.com/article"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:title>Introduction to Pottery</dc:title>
<dc:creator>Jane Smith</dc:creator>
</article>Note: - DublinCore namespace declared at root (hoisted) - No local xmlns declarations on title or creator
Declaration modes
Type namespaces support all namespace_scope declaration modes:
namespace_scope [
{ namespace: DublinCoreNamespace, declare: :auto }, # Default: declare if used
{ namespace: DcTermsNamespace, declare: :always }, # Always declare
]:auto(default)-
Declare only if type is actually used in attributes
:always-
Always declare, even if no attributes use the type
:never-
Never declare (error if type is used)
Advanced Patterns
Multi-namespace documents
Type namespaces excel in documents using multiple vocabularies:
# 1. Define namespaces
class CorePropertiesNamespace < Lutaml::Model::XmlNamespace
uri 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties'
prefix_default 'cp'
end
class DublinCoreNamespace < Lutaml::Model::XmlNamespace
uri 'http://purl.org/dc/elements/1.1/'
prefix_default 'dc'
end
class DCTermsNamespace < Lutaml::Model::XmlNamespace
uri 'http://purl.org/dc/terms/'
prefix_default 'dcterms'
end
# 2. Define types with namespaces
class DcTitleType < Lutaml::Model::Type::String
xml do
namespace DublinCoreNamespace
end
end
class DcCreatorType < Lutaml::Model::Type::String
xml do
namespace DublinCoreNamespace
end
end
class DctermsCreatedType < Lutaml::Model::Type::DateTime
xml do
namespace DCTermsNamespace
end
end
# 3. Use in model
class CoreProperties < Lutaml::Model::Serializable
attribute :title, DcTitleType
attribute :creator, DcCreatorType
attribute :created, DctermsCreatedType
xml do
root 'coreProperties'
namespace CorePropertiesNamespace
namespace_scope [
CorePropertiesNamespace,
DublinCoreNamespace,
DCTermsNamespace
]
map_element 'title', to: :title
map_element 'creator', to: :creator
map_element 'created', to: :created
end
end
props = CoreProperties.new(
title: 'Document Title',
creator: 'John Doe',
created: DateTime.parse('2024-01-01T12:00:00Z')
)
puts props.to_xmlOutput:
<coreProperties xmlns="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:dcterms="http://purl.org/dc/terms/">
<dc:title>Document Title</dc:title>
<dc:creator>John Doe</dc:creator>
<dcterms:created>2024-01-01T12:00:00+00:00</dcterms:created>
</coreProperties>Attribute type namespaces
Type namespaces work with XML attributes too:
class XsiTypeType < Lutaml::Model::Type::String
xml do
namespace XsiNamespace
xsd_type 'type'
end
end
class Product < Lutaml::Model::Serializable
attribute :schema_type, XsiTypeType
xml do
root 'product'
map_attribute 'type', to: :schema_type (1)
end
end
product = Product.new(schema_type: "ProductType")
puts product.to_xml| 1 | Attribute mapping automatically applies XsiNamespace |
Output:
<product xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="ProductType"/>Namespace resolution priority
Type namespaces fit into the namespace resolution priority:
-
Model-level namespace (highest)
# Child model declares its own namespace class Title < Lutaml::Model::Serializable xml do element 'title' namespace OverrideNamespace end end # Parent uses child's namespace class Document < Lutaml::Model::Serializable attribute :title, Title map_element 'title', to: :title # Uses Title's namespace end -
Type-level namespace
class TitleType < Lutaml::Model::Type::String xml do namespace DublinCoreNamespace end end attribute :title, TitleType map_element 'title', to: :title # Uses DublinCoreNamespace -
Form default (from
element_form_default)
Deserialization Support
Type namespaces work bidirectionally - both serialization (to_xml) and deserialization (from_xml).
xml = <<~XML
<article xmlns="http://example.com/article"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:title>Article Title</dc:title>
</article>
XML
# Parse XML
article = Article.from_xml(xml)
article.title # => "Article Title"
# Serialize back
article.to_xml
# => Same structure as input (round-trip)XSD Generation
Type namespaces are properly reflected in generated XSD:
xsd = Lutaml::Model::Schema.to_xml(Article)
puts xsdOutput:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:art="http://example.com/article"
xmlns:dc="http://purl.org/dc/elements/1.1/"
targetNamespace="http://example.com/article">
<xs:import namespace="http://purl.org/dc/elements/1.1/"
schemaLocation="..."/>
<xs:element name="article" type="art:ArticleType"/>
<xs:complexType name="ArticleType">
<xs:sequence>
<xs:element name="title" type="dc:titleType"/>
</xs:sequence>
</xs:complexType>
</xs:schema>Best Practices
When to use type namespaces
Use type-level namespaces when:
-
Type belongs to a well-defined vocabulary (e.g., Dublin Core, Schema.org)
-
Type will be reused across multiple models or projects
-
Namespace should be consistent wherever type is used
-
Working with standard metadata schemas
Don’t use type namespaces when:
-
Type is model-specific and won’t be reused
-
Namespace varies depending on context
-
Simple string/integer types without special namespace requirements
Namespace class organization
Create namespace classes once and reuse:
# lib/namespaces/dublin_core.rb
class DublinCoreNamespace < Lutaml::Model::XmlNamespace
uri "http://purl.org/dc/elements/1.1/"
schema_location "http://dublincore.org/schemas/xmls/qdc/dc.xsd"
prefix_default "dc"
element_form_default :qualified
documentation "Dublin Core Metadata Element Set"
end
# lib/types/dc_title_type.rb
class DcTitleType < Lutaml::Model::Type::String
xml_namespace DublinCoreNamespace
xsd_type 'titleType'
endCombine with xsd_type
Type namespaces work well with xsd_type for full XSD support:
class DcTitleType < Lutaml::Model::Type::String
xml_namespace DublinCoreNamespace # Namespace
xsd_type 'titleType' # XSD type name
def self.cast(value)
# Custom validation/normalization
value.to_s.strip
end
endTest both formats
Always test type namespace behavior with both default and prefix formats:
RSpec.describe Article do
let(:article) { Article.new(title: "Test") }
it "works with default format" do
xml = article.to_xml(prefix: false)
expect(xml).to include('<article xmlns=')
expect(xml).to include('<dc:title')
end
it "works with prefix format" do
xml = article.to_xml(prefix: true)
expect(xml).to include('<art:article')
expect(xml).to include('<dc:title')
end
it "round-trips correctly" do
parsed = Article.from_xml(article.to_xml)
expect(parsed.title).to eq(article.title)
end
endComplete Examples
Dublin Core metadata
# Namespace definition
class DublinCoreNamespace < Lutaml::Model::XmlNamespace
uri "http://purl.org/dc/elements/1.1/"
prefix_default "dc"
documentation "Dublin Core Metadata Element Set, Version 1.1"
end
# Type definitions
class DcTitleType < Lutaml::Model::Type::String
xml do
namespace DublinCoreNamespace
xsd_type 'titleType'
end
end
class DcCreatorType < Lutaml::Model::Type::String
xml do
namespace DublinCoreNamespace
xsd_type 'creatorType'
end
end
class DcSubjectType < Lutaml::Model::Type::String
xml do
namespace DublinCoreNamespace
xsd_type 'subjectType'
end
end
class DcDateType < Lutaml::Model::Type::Date
xml do
namespace DublinCoreNamespace
xsd_type 'dateType'
end
end
# Model using Dublin Core types
class BookMetadata < Lutaml::Model::Serializable
attribute :title, DcTitleType
attribute :creator, DcCreatorType, collection: true
attribute :subject, DcSubjectType, collection: true
attribute :date, DcDateType
xml do
root "metadata"
namespace_scope [DublinCoreNamespace]
map_element "title", to: :title
map_element "creator", to: :creator
map_element "subject", to: :subject
map_element "date", to: :date
end
end
# Usage
metadata = BookMetadata.new(
title: "The Art of Ceramics",
creator: ["Jane Smith", "John Doe"],
subject: ["pottery", "ceramics", "art"],
date: Date.new(2024, 1, 15)
)
puts metadata.to_xmlOutput:
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:title>The Art of Ceramics</dc:title>
<dc:creator>Jane Smith</dc:creator>
<dc:creator>John Doe</dc:creator>
<dc:subject>pottery</dc:subject>
<dc:subject>ceramics</dc:subject>
<dc:subject>art</dc:subject>
<dc:date>2024-01-15</dc:date>
</metadata>vCard with multiple type namespaces
# Namespace definitions
class VcardNamespace < Lutaml::Model::XmlNamespace
uri "urn:ietf:params:xml:ns:vcard-4.0"
prefix_default "vcard"
end
class DublinCoreNamespace < Lutaml::Model::XmlNamespace
uri "http://purl.org/dc/elements/1.1/"
prefix_default "dc"
end
class DcTermsNamespace < Lutaml::Model::XmlNamespace
uri "http://purl.org/dc/terms/"
prefix_default "dcterms"
end
# Type definitions
class DcTitleType < Lutaml::Model::Type::String
xml do
namespace DublinCoreNamespace
end
end
class DctermsCreatedType < Lutaml::Model::Type::DateTime
xml do
namespace DcTermsNamespace
end
end
class DctermsModifiedType < Lutaml::Model::Type::DateTime
xml do
namespace DcTermsNamespace
end
end
# vCard model
class Vcard < Lutaml::Model::Serializable
attribute :title, DcTitleType
attribute :created, DctermsCreatedType
attribute :modified, DctermsModifiedType
attribute :version, :string
xml do
root "vCard"
namespace VcardNamespace
namespace_scope [
VcardNamespace,
DublinCoreNamespace,
DcTermsNamespace
]
map_element "title", to: :title
map_element "created", to: :created
map_element "modified", to: :modified
map_element "version", to: :version
end
end
# Usage
vcard = Vcard.new(
title: "Dr. John Smith",
created: DateTime.parse("2024-01-01T10:00:00Z"),
modified: DateTime.parse("2024-06-01T15:30:00Z"),
version: "4.0"
)
puts vcard.to_xmlOutput:
<vCard xmlns="urn:ietf:params:xml:ns:vcard-4.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:dcterms="http://purl.org/dc/terms/">
<dc:title>Dr. John Smith</dc:title>
<dcterms:created>2024-01-01T10:00:00+00:00</dcterms:created>
<dcterms:modified>2024-06-01T15:30:00+00:00</dcterms:modified>
<version>4.0</version>
</vCard>Overriding type namespaces
Child model class namespace overrides type namespace:
class CustomNamespace < Lutaml::Model::XmlNamespace
uri "http://example.com/custom"
prefix_default "custom"
end
# Child model with its own namespace
class CustomTitle < Lutaml::Model::Serializable
attribute :value, :string
xml do
element "title"
namespace CustomNamespace
map_content to: :value
end
end
class Article < Lutaml::Model::Serializable
attribute :title, CustomTitle # Uses CustomTitle's namespace
xml do
root "article"
map_element "title", to: :title
end
end
article = Article.new(title: CustomTitle.new(value: "Test"))
puts article.to_xmlOutput:
<article>
<custom:title xmlns:custom="http://example.com/custom">Test</custom:title>
</article>Note: CustomNamespace used instead of DublinCoreNamespace from type
Common Patterns
Pattern 1: Standard vocabulary types
Create a library of reusable types for standard vocabularies:
# lib/types/dublin_core.rb
module DublinCore
NAMESPACE = Class.new(Lutaml::Model::XmlNamespace) do
uri "http://purl.org/dc/elements/1.1/"
prefix_default "dc"
end
class TitleType < Lutaml::Model::Type::String
xml do
namespace NAMESPACE
end
end
class CreatorType < Lutaml::Model::Type::String
xml do
namespace NAMESPACE
end
end
class SubjectType < Lutaml::Model::Type::String
xml do
namespace NAMESPACE
end
end
# ... more types
endPattern 2: Mixing namespaced and non-namespaced types
class Article < Lutaml::Model::Serializable
attribute :title, DcTitleType # Has namespace
attribute :abstract, :string # No namespace
attribute :keywords, :string, collection: true # No namespace
xml do
root "article"
namespace ArticleNamespace
namespace_scope [ArticleNamespace, DublinCoreNamespace]
map_element "title", to: :title # <dc:title>
map_element "abstract", to: :abstract # <abstract> (no namespace)
map_element "keyword", to: :keywords # <keyword> (no namespace)
end
endPattern 3: Type namespace with custom validation
class DcIdentifierType < Lutaml::Model::Type::String
xml do
namespace DublinCoreNamespace
xsd_type 'identifierType'
end
def self.cast(value)
id = super(value)
# Validate DOI format
unless id.match?(%r{^10\.\d{4,}/\S+$})
raise Lutaml::Model::TypeError, "Invalid DOI format: #{id}"
end
id
end
endTroubleshooting
Type namespace not appearing
Problem: Type namespace not showing in XML output
Solutions:
-
Verify type has namespace declared in
xml doblock:class MyType < Lutaml::Model::Type::String xml do namespace MyNamespace # Must be present end end -
Check attribute uses the type:
attribute :field, MyType # Not :string -
Ensure mapping exists:
map_element "field", to: :field # Must be mapped
Migration from Explicit Mapping
If you’re currently using explicit namespace on every mapping:
class Article < Lutaml::Model::Serializable
attribute :title, :string
attribute :creator, :string
xml do
map_element "title", to: :title,
namespace: DublinCoreNamespace
map_element "creator", to: :creator,
namespace: DublinCoreNamespace
end
endclass DcTitleType < Lutaml::Model::Type::String
xml do
namespace DublinCoreNamespace
end
end
class DcCreatorType < Lutaml::Model::Type::String
xml do
namespace DublinCoreNamespace
end
end
class Article < Lutaml::Model::Serializable
attribute :title, DcTitleType
attribute :creator, DcCreatorType
xml do
map_element "title", to: :title # Namespace from type
map_element "creator", to: :creator # Namespace from type
end
endBenefits: - Centralized namespace definition - Reusable across models - Consistent behavior - Easier to maintain
Implementation Architecture
As of Session 197 (2026-01-01), Type namespace handling follows the "Dumb Adapter" architectural principle:
- Planning Phase (DeclarationPlanner)
-
All Type namespace decisions are made during the planning phase: +
-
Detection:
DeclarationPlanner.plan_type_namespacesidentifies which attributes use Type namespaces -
Format: Type namespaces always use prefix format (never default)
-
Location: Respects
namespace_scopehoisting rules -
Result: Type namespaces added to
DeclarationPlan
-
- Resolution Phase (NamespaceResolver)
-
Adapters read Type namespace decisions from the plan: +
-
NamespaceResolver.resolve_for_elementextracts Type namespace info -
Returns
{prefix:, uri:, format:}hash -
NO decision-making logic in adapters
-
- Rendering Phase (Adapters)
-
Adapters apply decisions blindly: +
-
Read prefix from
DeclarationPlan -
Build element name:
"{prefix}:{element_name}" -
Add xmlns declarations if needed
-
NO Type namespace logic in adapters
-
This architecture ensures:
-
Consistency: Same logic across all adapters (Nokogiri, Oga, Ox)
-
Testability: Planning and rendering can be tested independently
-
Maintainability: Type namespace features added in one place only
-
No Duplication: xmlns filtering prevents redundant declarations
Migration from TypeNamespaceHandler
The TypeNamespaceHandler module is DEPRECATED as of Session 197. All Type namespace handling is now done by DeclarationPlanner. |
If you see references to TypeNamespaceHandler in older documentation or code:
Old approach (deprecated):
# In adapters (WRONG - violates Dumb Adapter principle):
type_ns = TypeNamespaceHandler.type_namespace_for(attr, rule, register)
prefix = TypeNamespaceHandler.prefix_for(type_ns, plan, options)Current approach (Session 197+):
# In DeclarationPlanner (CORRECT - planning phase):
plan_type_namespaces(element, mapping, plan)
# In adapters (CORRECT - rendering phase):
resolver = NamespaceResolver.new(@register)
ns_result = resolver.resolve_for_element(rule, attr, mapping, plan, options)
prefix = ns_result[:prefix]For more details, see Dumb Adapter Principle.
Related Documentation
-
XML Namespaces Guide - General namespace concepts
-
Three-Phase Architecture - Implementation details
-
Value Types - Custom type creation
-
Creating XSD - Schema generation with namespaces
Summary
Type-level namespaces provide a powerful way to:
-
Centralize namespace configuration in reusable type classes
-
Automate namespace application during serialization
-
Ensure consistency across all uses of the type
-
Support W3C-compliant round-trip processing
-
Generate proper XSD schemas with namespace imports
Use type namespaces when working with standard vocabularies or creating reusable type libraries. Combine with namespace_scope for optimal XML structure.