Right you are! d = lookup.reduce(data) { |hash, key| hash[key] }
works the same way. I don’t use JSON::Any
that much, so I’m happy to learn new things about it.
Here’s the official language reference documentation about union types.
Here’s another example, integrating what you just pointed out about JSON::Any
:
require "json"
data = JSON.parse %[{"data": {"food": [{"name": "pizza", "cost": 5}]}}]
lookup = ["data", "food", 0, "name"]
d = lookup.reduce(data) { |hash, key| hash[key] }
d = d.raw
p! d # => "pizza"
p! typeof(d) # => (Array(JSON::Any) | Bool | Float64 | Hash(String, JSON::Any) | Int64 | String | Nil)
Run it with carc.in
That last typeof
shows that the compile-time type of d
is JSON::Any::Type
(docs).
JSON::Any
provides some types for narrowing the type. For example, if you knew for absolute certain that d
(before you call #raw
) was a string value, you could just narrow it with JSON::Any#to_s
. However, you’re unlikely to be completely certain. In those cases, what you want to do depends on how you want to use the value.
If all you need is to output the value, you can just call #to_s
on it. If the value is a String
, that won’t even use any extra memory.
If you want to store the value, you’ll probably have to narrow the type eventually, and you can choose to do it before you store it, after you store it, or some combination.
As an example of the last one, if I knew that I’d end up with either a String
or an Int32
, I could use case
to narrow to String|Int32
:
... # previous code
case d
when String
value = d.as(String)
when Int32
value = d.as(Int32)
else
raise "Unexpected type in JSON lookup!"
end
p! value # => "pizza"
p! typeof(value) # => (Int32 | String)
Then I could store value
in an object or something; presumably I’d need to further narrow to either String
or Int32
when actually doing something with the value.
(Note that the raise
is important because it tells the compiler that the type couldn’t possibly be anything other than what is in the when
clauses. You could alternately use in
instead of when
, but you’d have to handle every single type in the union.)
If you want to completely narrow the type, you need to do whatever different logic you have for each type in the case
:
case d
when String
# do something when it's a String
when Int32
# do another thing when it's an Int32
else
raise "Unexpected type in JSON lookup!"
end
Handling types is a huge topic in Crystal, and I’m by no means a master of the language, but I hope that this provides something to start with.