Rails控制器使用render json时哈希键显示异常排查
问题根因
JSON规范明确要求所有对象的键必须是字符串类型,两个错误操作直接导致了渲染异常:
- 直接使用ActiveRecord实例(
OpsHeader对象)作为哈希键时,JSON序列化器会自动调用对象的to_s方法转成字符串,而OpsHeader的默认to_s输出就是#<OpsHeader:内存地址>格式,最终键就变成了无意义的对象标识。 - 后续把键替换为属性哈希后,Ruby原生哈希同样不支持嵌套哈希作为自身键,序列化时会把整个嵌套哈希直接转成无意义的字符串,才会出现一长串哈希内容当键的奇怪输出。
另外你写的双层嵌套循环遍历所有站点和行项的逻辑效率极低,数据量稍大就会出现严重的性能问题。
解决方案
1. 调整数据结构,符合JSON规范
不要使用ActiveRecord对象、嵌套哈希作为哈希键,两种常用的结构化方案可选:
方案A(推荐):使用数组承载所有站点的聚合信息
每个站点作为独立的数组元素,把行项数组作为站点信息的嵌套字段,不需要自定义键值映射,组装效率更高:
# 预加载关联行项,避免N+1查询 stops = OpsHeader .joins(ops_stop_rec: :driver_header) .includes(ops_stop_rec: :ops_line_items) .select( :pb_net_rev, :pbbname, :ops_driver1, :pb_id, :ops_stop_id, :dh_first_name, :dh_last_name ) .where(ops_stop_rec: { ops_arrive_time: params[:startDate]..params[:endDate] }) # 结构化组装返回结果 render json: stops.map do |stop| { # 展开站点基础属性,和line_items字段合并为单个哈希 **stop.attributes.slice( "pb_id", "pbbname", "pb_net_rev", "ops_driver1", "ops_stop_id", "dh_first_name", "dh_last_name" ), # 匹配站点下的行项数组,无匹配时可按需求返回空数组或null line_items: ( matched_lines = stop.ops_stop_rec.ops_line_items .select { |line| line.ops_stop_id == stop.ops_stop_id && line.ops_driver1 == stop.ops_driver1 } matched_lines.empty? ? nil : matched_lines.as_json(only: [:opl_amount, :ops_type, :ops_stop_id, :ops_order_id, :ops_driver1]) ) } end
方案B:保留哈希键值结构
如果一定要使用哈希做返回结构,就拿站点的唯一标识(如ops_stop_id)转成字符串当键,值为包含站点信息和行项的对象,符合JSON的键格式要求:
merger = {} stops.each do |stop| merger[stop.ops_stop_id.to_s] = { stop_info: stop.attributes.slice( "pb_id", "pbbname", "pb_net_rev", "ops_driver1", "ops_stop_id", "dh_first_name", "dh_last_name" ), line_items: nil } end # 后续把匹配的行项push到对应键的line_items数组里即可 # 最后直接 render json: merger
补充说明
你在binding.pry中看到的正常哈希结构,是Ruby原生哈希的表现——Ruby本身支持任意类型对象作为哈希键,这和JSON序列化的规则完全不同,pry中可正常查看的结构不代表能直接渲染为合法可读的JSON。
内容的提问来源于stack exchange,提问作者AttemptedMastery
相关产品推荐
相关产品推荐

