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
相关产品推荐
相关产品推荐

