【问题标题】:Rails isn't correctly rendering nested JSON results from Postgres JSON functionsRails 未正确呈现来自 Postgres JSON 函数的嵌套 JSON 结果
【发布时间】:2015-01-27 12:26:05
【问题描述】:

出于各种原因,我正在创建一个应用程序,它将 SQL 查询字符串作为 URL 参数并将其传递给 Postgres(类似于 CartDB SQL API 和 CFPB 的 Qu)。然后 Rails 呈现来自 Postgres 的结果的 JSON 响应。

来自我的控制器的片段:

@table = ActiveRecord::Base.connection.execute(@query)
render json: @table

这很好用。但是当我使用 Postgres JSON 函数(row_to_json、json_agg)时,它会将嵌套的 JSON 属性呈现为字符串。例如,以下查询:

query?q=SELECT max(municipal) AS series, json_agg(row_to_json((SELECT r FROM (SELECT sch_yr,grade_1 AS value ) r WHERE grade_1 IS NOT NULL))ORDER BY sch_yr ASC) AS values FROM ed_enroll WHERE grade_1 IS NOT NULL GROUP BY municipal

返回:

{
series: "Abington",
values: "[{"sch_yr":"2005-06","value":180}, {"sch_yr":"2005-06","value":180}, {"sch_yr":"2006-07","value":198}, {"sch_yr":"2006-07","value":198}, {"sch_yr":"2007-08","value":158}, {"sch_yr":"2007-08","value":158}, {"sch_yr":"2008-09","value":167}, {"sch_yr":"2008-09","value":167}, {"sch_yr":"2009-10","value":170}, {"sch_yr":"2009-10","value":170}, {"sch_yr":"2010-11","value":153}, {"sch_yr":"2010-11","value":153}, {"sch_yr":"2011-12","value":167}, {"sch_yr":"2011-12","value":167}]"
},
{
series: "Acton",
values: "[{"sch_yr":"2005-06","value":353}, {"sch_yr":"2005-06","value":353}, {"sch_yr":"2006-07","value":316}, {"sch_yr":"2006-07","value":316}, {"sch_yr":"2007-08","value":323}, {"sch_yr":"2007-08","value":323}, {"sch_yr":"2008-09","value":327}, {"sch_yr":"2008-09","value":327}, {"sch_yr":"2009-10","value":336}, {"sch_yr":"2009-10","value":336}, {"sch_yr":"2010-11","value":351}, {"sch_yr":"2010-11","value":351}, {"sch_yr":"2011-12","value":341}, {"sch_yr":"2011-12","value":341}]"
}

因此,它仅部分呈现 JSON,当我在查询中使用 Postgres 函数创建的嵌套 JSON 数组时遇到问题。

我不知道从哪里开始解决这个问题。有任何想法吗?我确定这是 Rails 的问题。

【问题讨论】:

    标签: ruby-on-rails json postgresql


    【解决方案1】:

    ActiveRecord::Base.connection.execute 不知道如何将数据库类型解压缩为 Ruby 类型,因此所有的东西——数字、布尔值、JSON 等等——你从它那里得到的都是一个字符串。如果您希望从控制器中输出合理的 JSON,则必须手动将 @table 中的数据转换为 Ruby 类型,然后以通常的方式将 Ruby 化的数据转换为 JSON。

    您的@table 实际上是一个PG::Result 实例,并且这些实例具有ftype(获取列类型)和fmod(获取列的类型修饰符)等方法,可以帮助您弄清楚什么数据排序在PG::Result 的每一列中。您可能会向PG::Result 询问每列的类型和修饰符,然后将它们交给format_type PostgreSQL 函数以获得一些可理解的类型字符串;然后您将这些类型字符串映射到转换方法并使用该映射来解压缩您返回的字符串。如果您深入研究 ActiveRecord 源代码,您会看到 AR 在做类似的事情。虽然 AR 源代码不适合胆小的人,但很抱歉,当您走出 AR 事物与数据库交互的狭窄范围时,这是课程的标准。

    您可能需要重新考虑“大量使用 SQL”的方法。如果您能找到自己构建 SQL 的方法,您可能会更轻松(并且能够在查询完成时将其列入白名单)。

    【讨论】:

      【解决方案2】:

      PG::Result 类(@table 的类型)利用TypeMaps 将结果值类型转换为 ruby​​ 对象。对于您的示例,您可以使用PG::TypeMapByColumn,如下所示:

      @table = ActiveRecord::Base.connection.execute(@query)
      @table.type_map = PG::TypeMapByColumn.new [nil, PG::TextDecoder::JSON.new]
      render json: @table
      

      更通用的方法是使用 PG::TypeMapByOid TypeMap 类。这要求您为每个 PG 属性类型提供 OID。这些列表可以在pg_type.dat 中找到。

      tm = PG::TypeMapByOid.new
      tm.add_coder PG::TextDecoder::Integer.new oid: 23
      tm.add_coder PG::TextDecoder::Boolean.new oid: 16
      tm.add_coder PG::TextDecoder::JSON.new oid: 114
      @table.type_map = tm
      

      【讨论】:

      • 只是为了让您知道上面的 TypeMaps 和 PG::TypeMapByColumn 链接不再起作用。
      • 对于任何想知道这个解决方案(最终迭代使用给定类型映射解析它们的记录)如何比使用JSON.parse(json_column) 解析 JSON 更快的人来说,这是因为 PG gem 的核心功能是编写的在 C 中,这意味着与 Ruby JSON 类相比,它快如闪电。这是我发现有用的链接。 msp-greg.github.io/pg/PG/Result.html#type_map-instance_method
      猜你喜欢
      • 2023-04-08
      • 1970-01-01
      • 1970-01-01
      • 2015-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-18
      • 2016-03-21
      相关资源
      最近更新 更多