7

I have a JSON response which is an Array of Hash:

[{"project" => {"id" => 1, "name" => "Internal"},
 {"project" => {"id" => 2, "name" => "External"}}]

My code looks like this:

client = HTTP::Client.new(url, ssl: true)
response = client.get("/projects", ssl: true)
projects = JSON.parse(response.body) as Array

This gives me an Array but it seems I need to typecast the elements to actually use them otherwise I get undefined method '[]' for Nil (compile-time type is (Nil | String | Int64 | Float64 | Bool | Hash(String, JSON::Type) | Array(JSON::Type))).

I tried as Array(Hash) but this gives me can't use Hash(K, V) as generic type argument yet, use a more specific type.

How do I specify the type?

Kris
  • 19,188
  • 9
  • 91
  • 111

2 Answers2

10

You have to cast these as you access the elements:

projects = JSON.parse(json).as(Array)
project = projects.first.as(Hash)["project"].as(Hash)
id = project["id"].as(Int64)

http://carc.in/#/r/f3f

But for well structured data like this you're better off with using JSON.mapping:

class ProjectContainer
  JSON.mapping({
    project: Project
  })
end

class Project
  JSON.mapping({
    id: Int64,
    name: String
  })
end

projects = Array(ProjectContainer).from_json(json)
project = projects.first.project
pp id = project.id

http://carc.in/#/r/f3g

You can see a slightly more detailed explanation of this problem at https://github.com/manastech/crystal/issues/982#issuecomment-121156428

Jonne Haß
  • 4,792
  • 18
  • 30
  • 1
    You beat me for 1 minute! – asterite Sep 13 '15 at 18:09
  • 1
    appears the #as syntax has changed? https://play.crystal-lang.org/#/r/1uhw or you can use to_i and to_s FWIW. – rogerdpack Apr 11 '17 at 23:00
  • According to this response https://stackoverflow.com/questions/52377857/how-to-configure-json-mapping-for-array-of-array-of-strings-to-become-a-hash/52389618#52389618 JSON.mapping is going to be removed in favor of JSON::Serializable – Guy C Sep 24 '18 at 01:22
3

You continue casting in every step:

projects = JSON.parse(response.body) as Array
projects.each do |project|
  project = project as Hash
  project = project["project"] as Hash
  id = project["id"] as Int
  name = project["name"] as String
end

But if your API response has a well-known structure, I strongly suggest you use JSON.mapping: https://crystal-lang.org/api/0.22.0/JSON.html#mapping-macro

asterite
  • 2,906
  • 1
  • 14
  • 14