module Lutaml::Model::Serialize::ClassMethods
def apply_child_mappings(hash, child_mappings)
def apply_child_mappings(hash, child_mappings) return hash unless child_mappings hash.map do |key, value| child_mappings.to_h do |attr_name, path| attr_value = if path == :key key elsif path == :value value else path = [path] unless path.is_a?(Array) value.dig(*path.map(&:to_s)) end [attr_name, attr_value] end end end
def apply_mappings(doc, format, options = {})
def apply_mappings(doc, format, options = {}) instance = options[:instance] || model.new return instance if !doc || doc.empty? return apply_xml_mapping(doc, instance, options) if format == :xml mappings = mappings_for(format).mappings mappings.each do |rule| attr = if rule.delegate attributes[rule.delegate].type.attributes[rule.to] else attributes[rule.to] end raise "Attribute '#{rule.to}' not found in #{self}" unless attr value = if doc.key?(rule.name) || doc.key?(rule.name.to_sym) doc[rule.name] || doc[rule.name.to_sym] else attr.default end if rule.custom_methods[:from] if value && !value.empty? value = new.send(rule.custom_methods[:from], instance, value) end next end value = apply_child_mappings(value, rule.child_mappings) value = attr.cast(value, format) if rule.delegate if instance.public_send(rule.delegate).nil? instance.public_send(:"#{rule.delegate}=", attributes[rule.delegate].type.new) end instance.public_send(rule.delegate).public_send(:"#{rule.to}=", value) else instance.public_send(:"#{rule.to}=", value) end end instance end
def apply_xml_mapping(doc, instance, options = {})
def apply_xml_mapping(doc, instance, options = {}) return instance unless doc mappings = mappings_for(:xml).mappings if doc.is_a?(Array) raise "May be `collection: true` is" \ "missing for #{self} in #{options[:caller_class]}" end if instance.respond_to?(:ordered=) && doc.is_a?(Lutaml::Model::MappingHash) instance.element_order = doc.item_order instance.ordered = mappings_for(:xml).mixed_content? || options[:mixed_content] end mappings.each do |rule| attr = attributes[rule.to] raise "Attribute '#{rule.to}' not found in #{self}" unless attr is_content_mapping = rule.name.nil? value = if is_content_mapping doc["text"] else doc[rule.name.to_s] || doc[rule.name.to_sym] end value = [value].compact if attr.collection? && !value.is_a?(Array) if value.is_a?(Array) value = value.map do |v| v.is_a?(Hash) && !(attr.type <= Serialize) ? v["text"] : v end elsif !(attr.type <= Serialize) && value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash value = value["text"] end unless is_content_mapping value = attr.cast( value, :xml, caller_class: self, mixed_content: rule.mixed_content, ) end if rule.custom_methods[:from] new.send(rule.custom_methods[:from], instance, value) else instance.public_send(:"#{rule.to}=", value) end end instance end
def attr_value(attrs, name, attr_rule)
def attr_value(attrs, name, attr_rule) value = if attrs.key?(name.to_sym) attrs[name.to_sym] elsif attrs.key?(name.to_s) attrs[name.to_s] else attr_rule.default end if attr_rule.collection? || value.is_a?(Array) (value || []).map do |v| if v.is_a?(Hash) attr_rule.type.new(v) else # TODO: This code is problematic because Type.cast does not know # about all the types. Lutaml::Model::Type.cast(v, attr_rule.type) end end else # TODO: This code is problematic because Type.cast does not know # about all the types. Lutaml::Model::Type.cast(value, attr_rule.type) end end
def attr_value_valid?(name, value)
def attr_value_valid?(name, value) attr = attributes[name] return true unless attr.options[:values] # Allow nil values if there's no default return true if value.nil? && !attr.default # Use the default value if the value is nil value = attr.default if value.nil? attr.options[:values].include?(value) end
def attribute(name, type, options = {})
def attribute(name, type, options = {}) attr = Attribute.new(name, type, options) attributes[name] = attr define_method(name) do instance_variable_get(:"@#{name}") end define_method(:"#{name}=") do |value| instance_variable_set(:"@#{name}", value) validate end end
def default_mappings(format)
def default_mappings(format) klass = format == :xml ? XmlMapping : KeyValueMapping klass.new.tap do |mapping| attributes&.each do |name, attr| mapping.map_element( name.to_s, to: name, render_nil: attr.render_nil?, ) end end end
def ensure_utf8(value)
def ensure_utf8(value) case value when String value.encode("UTF-8", invalid: :replace, undef: :replace, replace: "") when Array value.map { |v| ensure_utf8(v) } when Hash value.transform_keys do |k| ensure_utf8(k) end.transform_values do |v| ensure_utf8(v) end else value end end
def generate_hash_from_child_mappings(value, child_mappings)
def generate_hash_from_child_mappings(value, child_mappings) return value unless child_mappings hash = {} value.each do |child_obj| map_key = nil map_value = {} child_mappings.each do |attr_name, path| if path == :key map_key = child_obj.send(attr_name) elsif path == :value map_value = child_obj.send(attr_name) else 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, child_obj.send(attr_name)) end end hash[map_key] = map_value end hash end
def handle_delegate(instance, rule, hash, format)
def handle_delegate(instance, rule, hash, format) name = rule.to value = instance.send(rule.delegate).send(name) return if value.nil? && !rule.render_nil attribute = instance.send(rule.delegate).class.attributes[name] hash[rule.from] = attribute.serialize(value, format) end
def hash_representation(instance, format, options = {})
def hash_representation(instance, format, options = {}) only = options[:only] except = options[:except] mappings = mappings_for(format).mappings mappings.each_with_object({}) do |rule, hash| name = rule.to next if except&.include?(name) || (only && !only.include?(name)) next handle_delegate(instance, rule, hash, format) if rule.delegate if rule.custom_methods[:to] next instance.send(rule.custom_methods[:to], instance, hash) end value = instance.send(name) next if value.nil? && !rule.render_nil attribute = attributes[name] hash[rule.from] = if rule.child_mappings generate_hash_from_child_mappings(value, rule.child_mappings) else attribute.serialize(value, format, options) end end end
def inherited(subclass)
def inherited(subclass) super @mappings ||= {} @attributes ||= {} subclass.instance_variable_set(:@attributes, @attributes.dup) subclass.instance_variable_set(:@mappings, @mappings.dup) subclass.instance_variable_set(:@model, subclass) end
def mappings_for(format)
def mappings_for(format) mappings[format] || default_mappings(format) end
def model(klass = nil)
def model(klass = nil) if klass @model = klass else @model end end