一、当Ruby遇到序列化:为什么我们需要它

想象你正在搬家,要把心爱的乐高积木从客厅搬到卧室。直接用手抓肯定不行,这时候你需要工具箱——序列化就是程序员的"搬家工具"。在Ruby中,我们经常需要把对象转换成可以存储或传输的格式,比如保存到文件、发送给其他服务,这就是序列化的核心价值。

举个生活化的例子:你开发了一个电商系统,用户的购物车对象需要暂存到Redis。这时候就需要把Ruby对象变成Redis能理解的字符串。我们常用的两种方案是Marshal和JSON,它们就像搬家时的不同包装箱:

# 技术栈:Ruby 3.0+
# 示例1:基础序列化对比
require 'json'

cart = {user_id: 101, items: ['手机', '耳机'], total: 5999}

# Marshal序列化(二进制格式)
marshal_data = Marshal.dump(cart) 
# => "\x04\b{\b:\tuser_idi\x00e\x00:\nitems[\a\"\a手机\"\b耳机:\ntotali\xAE\x17"

# JSON序列化(文本格式)
json_data = JSON.generate(cart)
# => "{\"user_id\":101,\"items\":[\"手机\",\"耳机\"],\"total\":5999}"

可以看到Marshal生成的像天书,而JSON是人类可读的。这就像打包时用泡沫箱(Marshal)还是透明收纳盒(JSON)的区别。

二、Marshal:Ruby的"原生语言"

Marshal是Ruby自带的序列化模块,相当于会说Ruby方言的专家。它能完美处理Ruby特有的对象结构,比如Symbol、正则表达式这些JSON搞不定的类型。

# 示例2:处理复杂对象
class Product
  attr_accessor :name, :price
  def initialize(name, price)
    @name = name
    @price = price
  end
end

ruby_obj = {
  symbol: :vip,
  product: Product.new('键盘', 299),
  regex: /^[a-z]+$/i
}

# Marshal可以序列化任意Ruby对象
marshal_data = Marshal.dump(ruby_obj) 

# 反序列化后完全恢复原状
restored = Marshal.load(marshal_data)
restored[:regex].class # => Regexp

优势:

  • 保留完整的对象结构
  • 处理自定义类毫无压力
  • 速度通常比JSON快1.5-2倍

坑点提醒:

  1. 安全性:别加载不可信的Marshal数据,可能执行恶意代码
  2. 版本兼容:Ruby不同版本的Marshal格式可能有差异

三、JSON:通用"世界语"

JSON就像英语,是跨语言交流的通用协议。虽然表达能力不如Marshal丰富,但在Web开发中几乎无处不在。

# 示例3:JSON处理自定义对象
require 'json'

# 需要先定义as_json方法
class Product
  def as_json(*)
    {name: @name, price: @price}
  end
end

json_data = JSON.generate(
  user: '张三',
  product: Product.new('鼠标', 199)
)
# => "{\"user\":\"张三\",\"product\":{\"name\":\"鼠标\",\"price\":199}}"

关联技术: 在Rails中,ActiveRecord对象默认支持to_json,背后其实就是调用as_json。

性能优化技巧:

# 使用Oj(Optimized JSON)替代标准库
require 'oj'

# 比标准JSON快3-5倍
Oj.dump({data: '测试'}, mode: :compat)

四、决战紫禁之巅:如何选择

场景1:内部Ruby服务通信

  • 选Marshal:比如Sidekiq的作业队列
  • 原因:高效且保留完整的对象信息

场景2:Web API响应

  • 选JSON:比如提供给前端或移动端的接口
  • 原因:跨语言、可读性好

性能实测数据(Ruby 3.2):

require 'benchmark'

data = Array.new(1000) { |i| {id: i, value: rand(100)} }

Benchmark.bm do |x|
  x.report("Marshal") { 100.times { Marshal.dump(data) } }
  x.report("JSON")    { 100.times { JSON.dump(data) } }
  x.report("Oj")      { 100.times { Oj.dump(data) } }
end

# 结果示例:
#        user     system      total        real
# Marshal  0.020000   0.000000   0.020000 (  0.025801)
# JSON     0.040000   0.000000   0.040000 (  0.038215)
# Oj       0.010000   0.000000   0.010000 (  0.009843)

终极决策树:

  1. 需要跨语言?→ JSON
  2. 包含Ruby特有对象?→ Marshal
  3. 追求极致性能?→ 测试后选择

五、高级玩家的秘密武器

混合方案: 有些场景可以组合使用,比如用Marshal存储到Redis,同时提供JSON API:

# 示例4:混合使用方案
def cache_user_profile(user)
  # 内部缓存用Marshal
  redis.set("user:#{user.id}", Marshal.dump(user))
  
  # API响应用JSON
  Oj.dump(user.as_json, mode: :compat)
end

注意事项:

  • 大对象序列化会显著增加内存使用
  • 循环引用的对象需要特殊处理(JSON直接报错)
  • 考虑使用MessagePack等替代方案

六、总结与最佳实践

经过这一轮较量,我们得出以下结论:

  1. Marshal是Ruby程序的内功心法,适合内部数据流转
  2. JSON是对外交流的官方语言,尤其在Web领域不可替代
  3. 性能不是唯一指标,还要考虑安全性、可维护性

最后送大家一个彩蛋:Rails的Session默认使用Marshal序列化,这就是为什么你可以在session里存任意对象。但安全起见,生产环境建议改用加密的JSON或专门的安全方案。