You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ruby任意对象序列化存数据库 反序列化为原类实例方案

可行实现方案

该需求有完全匹配约束条件的落地实现方案,无需修改原有类代码、存储内容为人类可读的JSON格式、反序列化后可直接得到原始类实例,适配protobuf返回要求。

现有方案的局限性

你当前使用的「转哈希存JSON、读出来传构造方法」逻辑,仅能支持扁平结构、构造方法兼容哈希入参的简单类,遇到复杂场景会直接失效,核心问题包括:

  • 未存储原始类的类型信息,同列存储多类型对象时,读取后无法判断需要实例化的目标类
  • 嵌套属性不会自动转换为对应类实例,比如ClassA的属性包含ClassB实例时,to_h只会输出嵌套普通哈希,传入ClassA构造方法后无法得到ClassB类型的属性值
  • 部分类(包括部分protobuf生成类)的构造方法不支持直接传入平铺哈希参数,会触发参数错误
  • 强依赖类自带的to_h方法实现,遇到没有实现该方法的类会直接报错

具体实现逻辑

整体思路是在JSON中追加类型标记,通过递归逻辑处理嵌套结构,不侵入原有类的代码实现。

1. 约定存储结构

数据库中存储标准JSON格式,固定顶层两个字段,保证SQL直接查询时完全可读:

  • __type:字符串类型,存储原始类的完整类名(比如ClassA、MyModule::ClassC)
  • __attributes:对象类型,存储对象所有属性的序列化结果

2. 通用序列化逻辑(无需修改原有类)

不需要依赖目标类自带的序列化方法,通过通用逻辑提取属性、递归处理嵌套值:

# 配置需要支持序列化的类白名单
ALLOWED_SERIALIZE_CLASSES = [ClassA, ClassB, ClassC]

def extract_attributes(obj)
  # 优先复用类自带的attributes方法,天然适配ActiveRecord模型、Protobuf生成类
  return obj.attributes.transform_keys(&:to_s) if obj.respond_to?(:attributes)
  # 普通Ruby类兜底:遍历实例变量提取属性
  obj.instance_variables.each_with_object({}) do |var, attr_hash|
    attr_name = var.to_s.delete_prefix('@')
    attr_hash[attr_name] = obj.instance_variable_get(var)
  end
end

def serialize_value(value)
  case value
  when ->(v) { ALLOWED_SERIALIZE_CLASSES.any? { |c| v.is_a?(c) } }
    {
      "__type" => value.class.name,
      "__attributes" => extract_attributes(value).transform_values { |v| serialize_value(v) }
    }
  when Array
    value.map { |item| serialize_value(item) }
  when Hash
    value.transform_values { |v| serialize_value(v) }
  # 基础类型(字符串、数字、布尔、时间等)直接返回,特殊类型可自行加转换逻辑
  else
    value
  end
end

存库时直接调用序列化方法即可:

serialized_str = serialize_value(target_obj).to_json
active_record_obj.target_column = serialized_str
active_record_obj.save

3. 通用反序列化逻辑

读取JSON后递归还原嵌套结构,自动实例化对应类的对象:

def deserialize_value(value)
  case value
  when Hash
    # 识别带类型标记的对象结构
    if value.key?("__type") && value.key?("__attributes")
      target_class = Object.const_get(value["__type"])
      processed_attrs = deserialize_value(value["__attributes"]).transform_keys(&:to_sym)
      # 优先使用关键字参数初始化,适配绝大多数protobuf类、Struct类
      begin
        target_class.new(**processed_attrs)
      rescue ArgumentError
        # 构造方法不兼容时兜底:空实例化后逐个赋值实例变量,无侵入适配所有类
        instance = target_class.new
        processed_attrs.each do |attr_name, attr_val|
          instance.instance_variable_set(:"@#{attr_name}", attr_val)
        end
        instance
      end
    else
      # 普通哈希递归处理值
      value.transform_values { |v| deserialize_value(v) }
    end
  when Array
    value.map { |item| deserialize_value(item) }
  else
    value
  end
end

读取还原流程:

record = # 按条件查询数据库得到的记录
parsed_data = JSON.parse(record.target_column)
original_obj = deserialize_value(parsed_data)

最终得到的original_obj就是对应原始类的实例,可直接作为protobuf响应返回。

方案适配性说明

  • 满足无侵入要求:所有逻辑独立实现,不需要修改ClassA/ClassB/ClassC的任何代码,哪怕类来自第三方依赖、protobuf自动生成均可使用
  • 满足可读要求:存储内容为标准JSON,直接执行SQL查询可清晰看到对象类型和所有属性值,无二进制内容
  • 支持复杂嵌套:无论对象属性嵌套多少层自定义类实例,都能正确序列化和还原
  • 兼容性强:对构造方法参数要求特殊的类,通过实例变量赋值的兜底逻辑可正常实例化

内容的提问来源于stack exchange,提问作者Jonathan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 13:03:21