Class: Lutaml::KeyValue::Transformation::ValueSerializer

Inherits:
Object
  • Object
show all
Includes:
Model::RenderPolicy
Defined in:
lib/lutaml/key_value/transformation/value_serializer.rb

Overview

Serializes values (primitives and nested models) for key-value formats.

This is an independent class with explicit dependencies that can be tested in isolation from Transformation.

Examples:

Basic usage

serializer = ValueSerializer.new(
  format: :json,
  register_id: :default,
  transformation_factory: ->(type) { Transformation.new(type, ...) }
)
result = serializer.serialize_item(value, rule, options)

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Model::RenderPolicy

derived_attribute_for?, #should_skip_delegated_value?, #should_skip_value?

Constructor Details

#initialize(format:, register_id:, transformation_factory:, model_class: nil) ⇒ ValueSerializer

Initialize the ValueSerializer with explicit dependencies.

Parameters:

  • format (Symbol)

    The serialization format

  • register_id (Symbol, nil)

    The register ID

  • transformation_factory (Proc)

    Factory lambda ->(type_class) { Transformation }

  • model_class (Class, nil) (defaults to: nil)

    The model class for attribute lookup



40
41
42
43
44
45
46
# File 'lib/lutaml/key_value/transformation/value_serializer.rb', line 40

def initialize(format:, register_id:, transformation_factory:,
model_class: nil)
  @format = format
  @register_id = register_id
  @transformation_factory = transformation_factory
  @model_class = model_class
end

Instance Attribute Details

#formatSymbol (readonly)

Returns The serialization format (:json, :yaml, :toml).

Returns:

  • (Symbol)

    The serialization format (:json, :yaml, :toml)



23
24
25
# File 'lib/lutaml/key_value/transformation/value_serializer.rb', line 23

def format
  @format
end

#model_classClass? (readonly)

Returns The model class for attribute lookup.

Returns:

  • (Class, nil)

    The model class for attribute lookup



32
33
34
# File 'lib/lutaml/key_value/transformation/value_serializer.rb', line 32

def model_class
  @model_class
end

#register_idSymbol? (readonly)

Returns The register ID for attribute lookup.

Returns:

  • (Symbol, nil)

    The register ID for attribute lookup



26
27
28
# File 'lib/lutaml/key_value/transformation/value_serializer.rb', line 26

def register_id
  @register_id
end

#transformation_factoryProc (readonly)

Returns Factory lambda for creating child transformations.

Returns:

  • (Proc)

    Factory lambda for creating child transformations



29
30
31
# File 'lib/lutaml/key_value/transformation/value_serializer.rb', line 29

def transformation_factory
  @transformation_factory
end

Instance Method Details

#identity_fast_type(rule) ⇒ Object

Builtin scalars whose type registers no custom to_ serializer serialize as the value itself — Type::Value#to_'s default — without the wrap-allocate-cast ceremony per field.



196
197
198
199
200
201
202
203
204
205
# File 'lib/lutaml/key_value/transformation/value_serializer.rb', line 196

def identity_fast_type(rule)
  type = rule.attribute_type
  return nil unless type.is_a?(::Class) && type < ::Lutaml::Model::Type::Value

  serializer = ::Lutaml::Model::Type::Value
    .format_type_serializer_for(@format, type)
  has_custom_to = serializer && serializer[:to]
  has_custom_from = ::Lutaml::Model::Attribute.custom_from_probe?(type)
  type unless has_custom_to || has_custom_from
end

#nested_model?(rule) ⇒ Boolean

Check if a value is a nested model based on the rule.

Parameters:

  • rule (CompiledRule)

    The compiled rule

Returns:

  • (Boolean)

    true if the rule defines a nested model



99
100
101
102
103
# File 'lib/lutaml/key_value/transformation/value_serializer.rb', line 99

def nested_model?(rule)
  # Use include? instead of < because < returns nil (not false) for non-subclasses
  rule.attribute_type.is_a?(Class) &&
    rule.attribute_type.include?(Lutaml::Model::Serialize)
end

#serialize_item(value, rule, options = {}) ⇒ Object?

Serialize a value for an item (handles nested models and primitives).

This is the main entry point for value serialization.

Parameters:

  • value (Object)

    The value to serialize

  • rule (CompiledRule)

    The compiled rule

  • options (Hash) (defaults to: {})

    Serialization options

Returns:

  • (Object, nil)

    The serialized value (Hash, primitive, or nil)



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/lutaml/key_value/transformation/value_serializer.rb', line 56

def serialize_item(value, rule, options = {})
  return nil if value.nil?
  return nil if Lutaml::Model::Utils.uninitialized?(value)

  # Compiled serialize plan (TODO.perf/07): the rule-invariant
  # probes — identity-fast builtin scalars, Reference, union,
  # nested-model classification — resolve once per rule; the
  # per-field hash lookups dominated primitive-heavy docs.
  plan = serialize_plan(rule)
  if (fast = plan[:identity_type]) && value.instance_of?(fast)
    return value
  end

  # Check for Reference type first - even if value is a Serializable,
  # it should be serialized as a key, not as a nested model
  if plan[:reference]
    return serialize_reference(value, rule)
  end

  if plan[:union]
    serialize_union(value, options)
  elsif plan[:nested]
    serialize_nested_model(value, rule, options)
  else
    serialize_primitive(value, rule)
  end
end

#serialize_nested_model(value, rule, options = {}) ⇒ Hash?

Serialize a nested model to a hash representation.

Parameters:

  • value (Object)

    The model instance to serialize

  • rule (CompiledRule)

    The compiled rule

  • options (Hash) (defaults to: {})

    Serialization options

Returns:

  • (Hash, nil)

    The serialized hash or nil if empty



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/lutaml/key_value/transformation/value_serializer.rb', line 111

def serialize_nested_model(value, rule, options = {})
  validate_nested_model_type!(value, rule)

  # Determine the actual type for polymorphism support
  actual_type = determine_actual_type(value, rule)
  uses_polymorphism = actual_type != rule.attribute_type

  # Get or create child transformation
  child_transformation = if uses_polymorphism
                           create_transformation(actual_type)
                         else
                           rule.child_transformation ||
                             create_transformation(rule.attribute_type)
                         end

  if child_transformation
    transform_nested_model(value, child_transformation, options)
  else
    serialize_primitive(value, rule)
  end
end

#serialize_plan(rule) ⇒ Object



183
184
185
186
187
188
189
190
191
# File 'lib/lutaml/key_value/transformation/value_serializer.rb', line 183

def serialize_plan(rule)
  @serialize_plans ||= {}
  @serialize_plans[rule] ||= {
    identity_type: identity_fast_type(rule),
    reference: reference_type?(rule),
    union: ::Lutaml::Model::Type::Union.rule?(rule),
    nested: nested_model?(rule),
  }
end

#serialize_primitive(value, rule) ⇒ Object

Serialize a primitive value to the appropriate representation.

Parameters:

  • value (Object)

    The primitive value

  • rule (CompiledRule)

    The compiled rule

Returns:

  • (Object)

    The serialized value



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/lutaml/key_value/transformation/value_serializer.rb', line 138

def serialize_primitive(value, rule)
  return nil if value.nil?
  return nil if Lutaml::Model::Utils.uninitialized?(value)

  # For Reference types, use attribute's serialize method
  if reference_type?(rule)
    return serialize_reference(value, rule)
  end

  # For Serializable types, use to_#{format} method
  if rule.attribute_type.is_a?(Class) &&
      rule.attribute_type < Lutaml::Model::Serialize
    validate_serializable_type!(value, rule)
    return value.public_send(:"to_#{format}")
  end

  # Wrap value in type and call to_#{format}
  if rule.attribute_type.is_a?(Class) && rule.attribute_type < Lutaml::Model::Type::Value
    wrapped_value = rule.attribute_type.new(value)
    wrapped_value.public_send(:"to_#{format}")
  else
    value
  end
end

#serialize_union(value, options) ⇒ Object

Union: dispatch on the value's own class (stateless). A model member serializes via its class transformation; a scalar emits as-is.



86
87
88
89
90
91
92
93
# File 'lib/lutaml/key_value/transformation/value_serializer.rb', line 86

def serialize_union(value, options)
  unless value.is_a?(Lutaml::Model::Serialize)
    return Lutaml::Model::Type::Union.serialize_scalar(value, format)
  end

  transform_nested_model(value, create_transformation(value.class),
                         options)
end

#serialize_value_plan(rule) ⇒ Object

Per-rule serialize plan: the type class for builtin scalars with no custom to_ serializer (identity serialization), nil otherwise. Memoized per [rule]. Rule-invariant resolution for Transformation#serialize_value, memoized here (Transformation freezes itself after compile; the ValueSerializer shares its model_class/register_id).



169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/lutaml/key_value/transformation/value_serializer.rb', line 169

def serialize_value_plan(rule)
  @serialize_value_plans ||= {}
  @serialize_value_plans[rule] ||= begin
    attr = model_class.attributes(register_id)&.[](rule.attribute_name)
    attr ||= model_class.attributes&.[](rule.attribute_name)
    {
      attr: attr,
      reference: attr && attr.unresolved_type == Lutaml::Model::Type::Reference,
      nested: rule.attribute_type.is_a?(Class) &&
        rule.attribute_type < Lutaml::Model::Serialize,
    }
  end
end