module Lutaml::Model::Serialize
def self.included(base)
def self.included(base) base.extend(ClassMethods) base.initialize_attrs(base) end
def self.register_format_mapping_method(format)
def self.register_format_mapping_method(format) method_name = format == :hash ? :hsh : format ::Lutaml::Model::Serialize::ClassMethods.define_method(method_name) do |*args, &block| process_mapping(format, *args, &block) end end
def self.register_from_format_method(format)
def self.register_from_format_method(format) ClassMethods.define_method(:"from_#{format}") do |data, options = {}| from(format, data, options) end ClassMethods.define_method(:"of_#{format}") do |doc, options = {}| of(format, doc, options) end end
def self.register_to_format_method(format)
def self.register_to_format_method(format) ClassMethods.define_method(:"to_#{format}") do |instance, options = {}| to(format, instance, options) end ClassMethods.define_method(:"as_#{format}") do |instance, options = {}| as(format, instance, options) end define_method(:"to_#{format}") do |options = {}| to_format(format, options) end end
def attr_value(attrs, name, attribute)
def attr_value(attrs, name, attribute) value = Utils.fetch_str_or_sym(attrs, name, attribute.default(lutaml_register, self)) attribute.cast_value(value, lutaml_register) end
def attribute_exist?(name)
def attribute_exist?(name) name = name.to_s.chomp("=").to_sym if name.end_with?("=") self.class.attributes(lutaml_register).key?(name) end
def define_singleton_attribute_methods
Delegates to the class method which defines methods once per
Ensure register-specific attribute methods are defined on the class.
def define_singleton_attribute_methods self.class.ensure_register_methods_defined(lutaml_register) end
def determine_value(attrs, name, attr)
def determine_value(attrs, name, attr) if attrs.key?(name) || attrs.key?(name.to_s) return attr_value(attrs, name, attr) end resolved = attr.default_value(lutaml_register, self) return Lutaml::Model::UninitializedClass.instance if Lutaml::Model::Utils.uninitialized?(resolved) using_default_for(name) attr.cast_value(resolved, lutaml_register) end
def extract_register_id(attrs, options)
def extract_register_id(attrs, options) register = attrs&.dig(:lutaml_register) || options&.dig(:register) self.class.extract_register_id(register) end
def finalize_deserialization(register)
Called by allocate_for_deserialization to set up instance state,
Complete deserialization initialization after allocation.
def finalize_deserialization(register) init_deserialization_state(register) define_singleton_attribute_methods register_in_reference_store end
def init_deserialization_state(register)
Uses nil for @using_default to mean "all attributes use default" —
Called by allocate_for_deserialization instead of initialize.
Initialize instance state for fast deserialization path.
def init_deserialization_state(register) @using_default = nil @lutaml_register = register # Initialize all attributes to their "empty" state. # Collections use a shared frozen sentinel (zero allocation per instance). # Non-collections use UninitializedClass.instance (singleton, no allocation). # This ensures consistent initial state for the deserialization pipeline: # attributes that don't match any rule keep their UninitializedClass value, # avoiding the need for the setter to be called as a no-op. self.class.attributes(register).each do |name, attr| instance_variable_set(:"@#{name}", attr.collection? ? LAZY_EMPTY_COLLECTION : Lutaml::Model::UninitializedClass.instance) end end
def initialize(attrs = {}, options = {})
def initialize(attrs = {}, options = {}) @using_default = {} @lutaml_register = extract_register_id(attrs, options) return unless self.class.attributes(@lutaml_register) initialize_attributes(attrs, options) define_singleton_attribute_methods register_in_reference_store end
def initialize_attributes(attrs, options = {})
def initialize_attributes(attrs, options = {}) # Performance: Get value_map once for all attributes vmap = value_map(options) self.class.attributes(lutaml_register).each do |name, attr| next if attr.derived? value = determine_value(attrs, name, attr) default = using_default?(name) value = self.class.apply_value_map(value, vmap, attr) # Performance: Only call ensure_utf8 for string values value = self.class.ensure_utf8(value) if value.is_a?(::String) public_send(:"#{name}=", value) using_default_for(name) if default end end
def key_exist?(hash, key)
def key_exist?(hash, key) hash.key?(key.to_sym) || hash.key?(key.to_s) end
def key_value(hash, key)
def key_value(hash, key) hash[key.to_sym] || hash[key.to_s] end
def method_missing(method_name, *args)
def method_missing(method_name, *args) if method_name.to_s.end_with?("=") && attribute_exist?(method_name) define_singleton_method(method_name) do |value| instance_variable_set(:"@#{method_name.to_s.chomp('=')}", value) end send(method_name, *args) else super end end
def prepare_instance_format_options(_format, _options)
-
_options(Hash) -- Options hash (modified in place) -
_format(Symbol) -- The format
def prepare_instance_format_options(_format, _options) # No-op by default end
def pretty_print_instance_variables
def pretty_print_instance_variables reference_attributes = instance_variables.select do |var| var.to_s.end_with?("_ref") end (instance_variables - INTERNAL_ATTRIBUTES - reference_attributes).sort end
def register_in_reference_store
def register_in_reference_store Lutaml::Model::Store.register(self) if self.class.reference_resolvable? end
def resolve_reference_key(ref)
def resolve_reference_key(ref) return nil if ref.nil? return ref.map { |r| resolve_reference_key(r) } if ref.is_a?(Array) ref.is_a?(Type::Reference) ? ref.key : ref end
def resolve_reference_value(ref)
def resolve_reference_value(ref) return nil if ref.nil? return ref.map { |r| resolve_reference_value(r) } if ref.is_a?(Array) ref.is_a?(Type::Reference) ? ref.object : ref end
def resolve_register_attr(name)
Used by class-level setter methods to get the correct Attribute
Resolve the Attribute object for a register-specific attribute.
def resolve_register_attr(name) self.class.register_records[lutaml_register]&.dig(:attributes, name) || self.class.attributes[name] end
def respond_to_missing?(method_name, include_private = false)
def respond_to_missing?(method_name, include_private = false) (method_name.to_s.end_with?("=") && attribute_exist?(method_name)) || super end
def to_format(format, options = {})
def to_format(format, options = {}) # Hook for format-specific validation (e.g., XML root mapping check) validate_root_mapping!(format, options) # Pass instance's lutaml_register if not explicitly provided options[:register] ||= lutaml_register if lutaml_register # Hook for format-specific options preparation # XML overrides to handle prefix, doctype, declaration, namespaces prepare_instance_format_options(format, options) self.class.to(format, self, options) end
def to_yaml_hash
def to_yaml_hash self.class.as_yaml(self) end
def using_default?(attribute_name)
def using_default?(attribute_name) # nil means "all attributes using default" — return true without allocation return true if @using_default.nil? @using_default[attribute_name] end
def using_default_for(attribute_name)
def using_default_for(attribute_name) @using_default ||= ::Hash.new(true) @using_default[attribute_name] = true end
def validate_attribute!(attr_name)
def validate_attribute!(attr_name) attr = self.class.attributes[attr_name] value = instance_variable_get(:"@#{attr_name}") attr.validate_value!(value, lutaml_register, instance_object: self) end
def validate_root_mapping!(_format, _options)
-
_options(Hash) -- Options hash -
_format(Symbol) -- The format
def validate_root_mapping!(_format, _options) # No-op by default end
def value_map(options)
def value_map(options) # Fast path: return default map if no custom options return DEFAULT_VALUE_MAP if options.equal?(Type::Value::EMPTY_OPTIONS) return DEFAULT_VALUE_MAP if options.empty? # Slow path: merge with custom options { omitted: options[:omitted] || :nil, nil: options[:nil] || :nil, empty: options[:empty] || :empty, } end
def value_set_for(attribute_name)
def value_set_for(attribute_name) # Only allocate hash when transitioning from "all defaults" (nil) # Hash.new(true) ensures unset keys still return true @using_default ||= ::Hash.new(true) @using_default[attribute_name] = false end
def values_set_for(attribute_names)
def values_set_for(attribute_names) @using_default ||= ::Hash.new(true) attribute_names.each { |name| @using_default[name] = false } end