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

(class, register) combination instead of per-instance singleton 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)

define register-specific methods, and register in the reference store.
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)

no hash allocation needed until value_set_for is called.
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)

rubocop:disable Style/ArgumentsForwarding -- anonymous * requires Ruby 3.2+, but required_ruby_version >= 3.0
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)

Parameters:
  • _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)

for the instance's active register.
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)

Parameters:
  • _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