class Lutaml::Model::KeyValueTransform
def build_child_hash(key, value, child_mappings, attr, format)
def build_child_hash(key, value, child_mappings, attr, format) child_mappings.to_h do |attr_name, path| attr_value = extract_attr_value(path, key, value) attr_rule = attr.type.mappings_for(format).find_by_to(attr_name) [attr_rule.from.to_s, attr_value] end end
def cast_value(value, attr, format, rule)
def cast_value(value, attr, format, rule) cast_options = rule.polymorphic ? { polymorphic: rule.polymorphic } : {} attr.cast(value, format, cast_options) end
def child_mapping_for(name, mappings)
def child_mapping_for(name, mappings) mappings.find_by_to(name)&.name.to_s || name.to_s end
def convert_to_format(doc, format)
def convert_to_format(doc, format) adapter = Lutaml::Model::Config.adapter_for(format) adapter.new(doc).public_send(:"to_#{format}") end
def data_to_model(data, format, options = {})
def data_to_model(data, format, options = {}) instance = model_class.new mappings = extract_mappings(options, format) mappings.each do |rule| process_mapping_rule(data, instance, format, rule, options) end instance end
def extract_attr_value(path, key, value)
def extract_attr_value(path, key, value) case path when :key then key when :value then value else path = Array(path) value.dig(*path.map(&:to_s)) end end
def extract_hash_for_child_mapping(child_mappings, child_obj, rules)
def extract_hash_for_child_mapping(child_mappings, child_obj, rules) key = nil value = {} child_mappings.each do |attr_name, path| rule = rules.find_by_to(attr_name) attr_value = normalize_attribute_value(child_obj.send(attr_name)) next unless rule&.render?(attr_value, nil) next key = attr_value if path == :key value = extract_hash_value_for_child_mapping(path, attr_value, value) end value = nil if value.empty? { key => value } end
def extract_hash_value_for_child_mapping(path, value, map_value)
def extract_hash_value_for_child_mapping(path, value, map_value) return value if path == :value path = [path] unless path.is_a?(Array) path[0...-1].inject(map_value) do |acc, k| acc[k.to_s] ||= {} end.public_send(:[]=, path.last.to_s, value) map_value end
def extract_mappings(options, format)
def extract_mappings(options, format) options[:mappings] || mappings_for(format).mappings end
def extract_rule_value(doc, rule, format, attr)
def extract_rule_value(doc, rule, format, attr) rule_names = rule.multiple_mappings? ? rule.name : [rule.name] rule_names.each do |rule_name| value = rule_value_for(rule_name, doc, rule, format, attr) return value if Utils.initialized?(value) end Lutaml::Model::UninitializedClass.instance end
def extract_value_for_delegate(instance, rule)
def extract_value_for_delegate(instance, rule) instance.send(rule.delegate).send(rule.to) end
def generate_hash_from_child_mappings(attr, value, format, child_mappings)
def generate_hash_from_child_mappings(attr, value, format, child_mappings) return value unless child_mappings hash = {} generate_remaining_mappings_for_value(child_mappings, value, format) value.each do |child_obj| rules = attr.type.mappings_for(format) hash.merge!( extract_hash_for_child_mapping(child_mappings, child_obj, rules), ) end hash end
def generate_remaining_mappings_for_value(child_mappings, value, format)
If any additional mappings (e.g., { name: :key, id: :identifier })
the :key mapping (e.g., { name: :key }) is provided.
Generates remaining child mappings for all attributes when only
def generate_remaining_mappings_for_value(child_mappings, value, format) return if child_mappings.values != [:key] klass = value.first.class mappings = klass.mappings_for(format) klass.attributes.each_key do |name| next if Utils.string_or_symbol_key?(child_mappings, name) child_mappings[name.to_sym] = child_mapping_for(name, mappings) end end
def handle_delegate(instance, rule, hash, format)
def handle_delegate(instance, rule, hash, format) value = extract_value_for_delegate(instance, rule) return if value.nil? && !rule.render_nil attribute = instance.send(rule.delegate).class.attributes[rule.to] hash[rule_from_name(rule)] = attribute.serialize(value, format) end
def handle_raw_mapping(hash, value, format, options)
def handle_raw_mapping(hash, value, format, options) result = Lutaml::Model::Config.adapter_for(format).parse(value, options) hash.merge!(result) end
def handle_root_mappings(hash, value, format, rule, attr)
def handle_root_mappings(hash, value, format, rule, attr) hash.merge!( generate_hash_from_child_mappings( attr, value, format, rule.root_mappings, ), ) end
def map_child_data(child_hash, attr, format)
def map_child_data(child_hash, attr, format) self.class.data_to_model( attr.type, child_hash, format, { mappings: attr.type.mappings_for(format).mappings }, ) end
def model_to_data(instance, format, options = {})
def model_to_data(instance, format, options = {}) mappings = mappings_for(format).mappings mappings.each_with_object({}) do |rule, hash| next unless valid_mapping?(rule, options) next handle_delegate(instance, rule, hash, format) if rule.delegate process_mapping_for_instance(instance, hash, format, rule, options) end end
def normalize_attribute_value(value)
def normalize_attribute_value(value) if value.is_a?(Lutaml::Model::Serialize) value.to_hash elsif value.is_a?(Array) && value.first.is_a?(Lutaml::Model::Serialize) value.map(&:to_hash) else value end end
def only_keys_mapped?(child_mappings, hash)
def only_keys_mapped?(child_mappings, hash) child_mappings.values == [:key] && hash.values.all?(Hash) end
def process_child_mapping(key, value, child_mappings, attr, format, hash)
def process_child_mapping(key, value, child_mappings, attr, format, hash) child_hash = build_child_hash(key, value, child_mappings, attr, format) if only_keys_mapped?(child_mappings, hash) child_hash.merge!(value) end map_child_data(child_hash, attr, format) end
def process_custom_method(rule, instance, value)
def process_custom_method(rule, instance, value) return unless Utils.present?(value) model_class.new.send(rule.custom_methods[:from], instance, value) end
def process_mapping_for_instance(instance, hash, format, rule, options)
def process_mapping_for_instance(instance, hash, format, rule, options) if rule.custom_methods[:to] return instance.send(rule.custom_methods[:to], instance, hash) end attribute = attributes[rule.to] value = rule.serialize(instance) return handle_raw_mapping(hash, value, format, options) if rule.raw_mapping? return handle_root_mappings(hash, value, format, rule, attribute) if rule.root_mapping? value = ExportTransformer.call(value, rule, attribute) value = serialize_value(value, rule, attribute, format, options) return unless rule.render?(value, instance) value = apply_value_map(value, rule.value_map(:to, options), attribute) hash[rule_from_name(rule)] = value end
def process_mapping_rule(doc, instance, format, rule, options = {})
def process_mapping_rule(doc, instance, format, rule, options = {}) raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule) attr = attribute_for_rule(rule) return if attr&.derived? value = extract_rule_value(doc, rule, format, attr) value = apply_value_map(value, rule.value_map(:from, options), attr) return process_custom_method(rule, instance, value) if rule.has_custom_method_for_deserialization? value = translate_mappings(value, rule.hash_mappings, attr, format) value = cast_value(value, attr, format, rule) unless rule.hash_mappings attr.valid_collection!(value, context) rule.deserialize(instance, value, attributes, self) end
def rule_from_name(rule)
def rule_from_name(rule) rule.multiple_mappings? ? rule.from.first.to_s : rule.from.to_s end
def rule_value_for(name, doc, rule, format, attr)
def rule_value_for(name, doc, rule, format, attr) if rule.root_mapping? doc elsif rule.raw_mapping? convert_to_format(doc, format) elsif Utils.string_or_symbol_key?(doc, name) Utils.fetch_with_string_or_symbol_key(doc, name) elsif attr&.default_set? attr.default else Lutaml::Model::UninitializedClass.instance end end
def serialize_value(value, rule, attr, format, options)
def serialize_value(value, rule, attr, format, options) return attr.serialize(value, format, options) unless rule.child_mappings generate_hash_from_child_mappings(attr, value, format, rule.child_mappings) end
def translate_mappings(hash, child_mappings, attr, format)
def translate_mappings(hash, child_mappings, attr, format) return hash unless child_mappings hash.map do |key, value| process_child_mapping(key, value, child_mappings, attr, format, hash) end end
def valid_mapping?(rule, options)
def valid_mapping?(rule, options) only = options[:only] except = options[:except] name = rule.to (except.nil? || !except.include?(name)) && (only.nil? || only.include?(name)) end