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)

Constant Summary

Constants included from Model::RenderPolicy

Model::RenderPolicy::SKIP_PLANS

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

#format ⇒ Symbol (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_class ⇒ Class? (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_id ⇒ Symbol? (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_factory ⇒ Proc (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

#custom_to_fn(rule) ⇒ Object



210
211
212
213
214
215
216
# File 'lib/lutaml/key_value/transformation/value_serializer.rb', line 210

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

  ::Lutaml::Model::Type::Value
    .format_type_serializer_for(@format, type)&.fetch(:to, nil)
end

#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.



221
222
223
224
225
226
227
228
229
230
# File 'lib/lutaml/key_value/transformation/value_serializer.rb', line 221

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



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

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),
    # The custom to_<format> serializer, resolved once per rule:
    # the default path is pure identity (to_<format> returns the
    # wrapped .value), so values without a custom serializer skip
    # the wrap-allocate-cast ceremony entirely, and custom ones
    # skip the per-value registry lookup.
    to_fn: custom_to_fn(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
162
163
164
165
166
167
168
169
170
171
172
# 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

  if rule.attribute_type.is_a?(Class) && rule.attribute_type < Lutaml::Model::Type::Value
    plan = serialize_plan(rule)
    # Identity only under identity_fast_type's exact condition
    # (no custom to AND no custom from): a custom from means the
    # wrapper constructor normalizes the value, so the raw value
    # must still pass through it.
    if plan[:identity_type] && value.instance_of?(plan[:identity_type])
      return value
    end
    if (to_fn = plan[:to_fn])
      return to_fn.call(rule.attribute_type.new(value))
    end

    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).



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

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