Ruby on Rails:如何使用JSONPath访问并保存到数据库JSON数组中的嵌套对象/属性?

问题描述

我正在尝试将数据从JSON对象数组播种到数据库中。我有两个单独的数据库表-属性和单位(一个属性有许多单位)。通过API请求JSON数据,然后将其播种到数据库,我已经能够成功地针对属性信息(属性模型)执行此操作,但是ValuationReport属性在我的前端显示为字符串形式的JSON对象。>

以下是我正在使用的JSON数据的示例(出于演示目的,我已将该示例限制为3个对象的数组):

[
  {
    "PublicationDate": "17/09/2019","PropertyNumber": 2198231,"County": "CAVAN","LocalAuthority": "CAVAN COUNTY COUNCIL","Valuation": 15090,"Category": "OFFICE","Uses": "OFFICE (OVER THE SHOP),-","Address1": "77(1ST & 2ND FL) MAIN STREET","Address2": "CAVAN","Address3": "CO. CAVAN","Address4": "","Address5": "","CarPark": 0,"Xitm": 641927.73,"Yitm": 804638.3,"ValuationReport": [
      {
        "Level": "1","FloorUse": "RESTAURANT","Area": 112.25,"NavPerM2": 80,"Nav": 8980
      },{
        "Level": "2","Area": 98.57,"NavPerM2": 62,"Nav": 6111.34
      }
    ]
  },{
    "PublicationDate": "17/09/2019","PropertyNumber": 1558322,"Valuation": 10200,"Uses": "OFFICE (OWN DOOR),"Address1": "23E MAIN STREET","Xitm": 641941.19,"Yitm": 804875.56,"ValuationReport": [
      {
        "Level": "0","FloorUse": "OFFICE(S)","Area": 127.55,"Nav": 10204
      }
    ]
  },"PropertyNumber": 2116802,"Valuation": 15140,HERITAGE / INTERPRETATIVE CENTRE","Address1": "LOCAL NO/MAP REF: 7AB","Address2": "MULLAGH","Address3": "BAILIEBORO CO. CAVAN","Xitm": 668656.19,"Yitm": 785281.05,"Area": 252.49,"NavPerM2": 60,"Nav": 15149.4
property_id: unit[property.id]
      }
    ]
  }
]

我遇到的困难是ValuationReport属性,它是JSON属性数组中每个元素的大小可变的数组。因此,某些属性将具有仅包含一个对象的ValuationReport数组,而其他属性将包含具有多个对象的ValuationReport对象。我想将每个ValuationReport数组中的每个嵌套对象保存到数据库(单元模型)中的单独单元中。

我已经使用JSONPath Gem尝试访问所需的元素,但是我做错了事。

当我运行'$ rails db:seed'时,它一直运行到一个点,并且出现以下错误

Creating property 2198231
Creating unit.
rails aborted!
TypeError: no implicit conversion of String into Integer
/db/seeds.rb:62:in `[]'
/db/seeds.rb:62:in `block (2 levels) in properties'
/db/seeds.rb:58:in `each'
/db/seeds.rb:58:in `block in properties'
/db/seeds.rb:36:in `each'
/db/seeds.rb:36:in `properties'
/db/seeds.rb:207:in `<top (required)>'
bin/rails:4:in `require'
bin/rails:4:in `<main>'
Tasks: TOP => db:seed
(See full trace by running task with --trace)

以下是我的宝石文件中的相关宝石:

# REST Client for APIs
gem 'rest-client','~> 2.1'

# JSONPath - for acessing values in nested JSON objects.
gem 'jsonpath','~> 0.5.8'

我的数据库架构如下:

ActiveRecord::Schema.define(version: 2020_11_07_130018) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "properties",force: :cascade do |t|
    t.date "publication_date"
    t.string "property_number"
    t.string "county"
    t.string "local_authority"
    t.decimal "valuation"
    t.string "category"
    t.string "uses"
    t.string "address_1"
    t.string "address_2"
    t.string "address_3"
    t.string "address_4"
    t.string "address_5"
    t.decimal "car_park"
    t.decimal "xitm"
    t.decimal "yitm"
    t.datetime "created_at",precision: 6,null: false
    t.datetime "updated_at",null: false
  end

  create_table "units",force: :cascade do |t|
    t.string "level"
    t.string "floor_use"
    t.decimal "area"
    t.decimal "nav_per_m2"
    t.decimal "nav"
    t.bigint "property_id"
    t.datetime "created_at",null: false
    t.index ["property_id"],name: "index_units_on_property_id"
  end

  add_foreign_key "units","properties"
end

我的seed.rb文件在下面。我尝试使用索引(i)来确保对象ValuationReport对象数组的循环为每个属性循环一次,并创建每个属性内存在的所有单元。

require 'rest-client'
require 'json'
require 'jsonpath'

# Define Method - API Request
def properties

    response = RestClient.get('https://api.valoff.ie/api/Property/GetProperties?Fields=*&LocalAuthority=CAVAN%20COUNTY%20COUNCIL&CategorySelected=OFFICE&Format=csv&Download=false')
    json = JSON.parse(response)

    json.each do |property|
        puts "Creating property #{property['PropertyNumber']}"

        Property.create!(

            publication_date: property['PublicationDate'],property_number: property['PropertyNumber'],county: property['County'],local_authority: property['LocalAuthority'],valuation: property['Valuation'],category: property['Category'],uses: property['Uses'],address_1: property['Address1'],address_2: property['Address2'],address_3: property['Address3'],address_4: property['Address4'],address_5: property['Address5'],car_park: property['CarPark'],xitm: property['Xitm'],yitm: property['Yitm'],units: 
                JsonPath.new('$.ValuationReport').on(property).each do |unit|
                    puts "Creating unit."
                    Unit.create!(
                        level: unit['$.Level'],floor_use: unit['$.FloorUse'],area: unit['$.Area'],nav_per_m2: unit['$.NavPerM2'],nav: unit['$.Nav'],property_id: unit['property.id']
                    )
                end
        )
    end
end

# Call Method
properties

感谢您的时间和帮助!

解决方法

在这种情况下,您不需要JsonPath。我更新了您的脚本:

  • 删除了JsonPath,转而使用property['ValuationReport']
  • 从json键中删除了$.
  • 首先您应该创建一个属性,然后以单位为单位使用其ID(或者,您可以在模型中设置accepts_nested_attributes
require 'rest-client'
require 'json'
require 'jsonpath'

# Define Method - API Request
def properties

    response = RestClient.get('https://api.valoff.ie/api/Property/GetProperties?Fields=*&LocalAuthority=CAVAN%20COUNTY%20COUNCIL&CategorySelected=OFFICE&Format=csv&Download=false')
    json = JSON.parse(response)

    json.each do |property|
        puts "Creating property #{property['PropertyNumber']}"

        property_model = Property.create!(

            publication_date: property['PublicationDate'],property_number: property['PropertyNumber'],county: property['County'],local_authority: property['LocalAuthority'],valuation: property['Valuation'],category: property['Category'],uses: property['Uses'],address_1: property['Address1'],address_2: property['Address2'],address_3: property['Address3'],address_4: property['Address4'],address_5: property['Address5'],car_park: property['CarPark'],xitm: property['Xitm'],yitm: property['Yitm']
        )


        property['ValuationReport'].each do |unit|
          puts "Creating unit."
          property_model.units.create!(
            level: unit['Level'],floor_use: unit['FloorUse'],area: unit['Area'],nav_per_m2: unit['NavPerM2'],nav: unit['Nav']
          )
        end
    end
end

# Call Method
properties