Ruby 解析 JSON 完全指南:读取、转换、生成与错误处理
本文系统讲解 Ruby JSON 处理:JSON.parse 与 JSON.generate 的正确用法、符号键、嵌套数据提取、解析错误处理,以及 GB 级大文件的流式解析方案。
Ruby 标准库 JSON 模块用 JSON.parse 把文本转成 Hash/Array,用 JSON.generate 反向生成。生产代码三要素:解析必须 rescue JSON::ParserError、外部数据别用 symbolize_names、大文件用分块或流式解析避免内存爆炸。
解析的基本动作
require 'json'
data = JSON.parse('{"name":"观测云","users":[{"id":1}]}')
data['name'] # => "观测云"
data['users'].first['id'] # => 1
JSON 对象变 Hash、数组变 Array、字符串数字布尔值各归各位——解析完就是普通 Ruby 数据结构,后续处理全是 Enumerable 的拿手好戏。
从文件读取与生成输出
config = JSON.parse(File.read('config.json'))
File.write('out.json', JSON.pretty_generate(data)) # 美化输出
File.write('out.json', JSON.generate(data)) # 紧凑输出
pretty_generate 带缩进,适合配置文件;接口传输和日志用 generate 紧凑格式省空间。
符号键的取舍
JSON.parse('{"a":1}', symbolize_names: true) # => {:a=>1}
符号键省去后续转换,但只用于可信数据。解析用户上传或第三方接口返回时保持字符串键——恶意构造的十万种不同键会造成无谓的 symbol 膨胀。Rails 项目里更推荐 HashWithIndifferentAccess,两种访问方式兼得且没有 symbol 风险。
嵌套数据的提取技巧
深层取值最怕中间层是 nil:
data.dig('user', 'profile', 'city') # 任一层缺失返回 nil 而非报错
dig 是处理不确定结构的安全网;再复杂的路径(如"所有订单里金额大于 100 的商品名")用 flat_map + select 组合,或引入 jsonpath 类库。
错误处理:健壮解析的标配
def parse_payload(raw)
JSON.parse(raw)
rescue JSON::ParserError => e
logger.warn("非法 JSON:#{e.message[0..100]}")
nil
rescue Encoding::CompatibilityError
JSON.parse(raw.force_encoding('UTF-8').scrub)
end
两个高频坑:一是第三方返回 HTML 错误页而非 JSON(先查 HTTP 状态码再解析);二是编码混杂,必要时 scrub 清理非法字节。另外注意 JSON.parse('null') 返回 nil、JSON.parse('') 直接抛错,空响应体要先判空。
大文件与流式处理
几百 MB 的 JSON 文件不能一把梭 JSON.parse(File.read(f)):
- JSON Lines 格式(每行一个对象):
File.foreach(f) { |line| process(JSON.parse(line)) },内存恒定,导出文件首选此格式; - 巨型单 JSON 文档:换 Oj 的 SAX 式回调解析(
Oj::ScHandler),或先用 jq 命令行预切片再分块处理。
常见问题(FAQ)
Q:JSON.parse 和 JSON.load 有什么区别?
JSON.load 会还原序列化的 Ruby 对象(可被用来实例化任意类),存在反序列化攻击面,解析外部数据只用 parse。load 留给完全可信的内部数据。
Q:Time/Date 对象序列化后变成什么?
默认变成字符串(to_s 结果),格式未必符合下游预期。约定俗成的做法是显式 iso8601:JSON.generate(t: Time.now.iso8601),解析端 Time.iso8601(str) 还原。
Q:Rails 里该用 to_json 还是 as_json?
as_json 返回可序列化的 Hash 结构(中间层),to_json 输出最终字符串。自定义序列化逻辑覆盖 as_json,让 to_json 自动复用;接口复杂时直接上 Jbuilder 或 Alba 等序列化器。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。