Json in Crystal

Our program will demonstrate JSON encoding and decoding in Crystal, including handling of basic data types and custom structures.

require "json"

# We'll use these two structs to demonstrate encoding and
# decoding of custom types below.
class Response1
  include JSON::Serializable
  property page : Int32
  property fruits : Array(String)
end

# You can use annotations on struct field declarations
# to customize the encoded JSON key names.
class Response2
  include JSON::Serializable
  @[JSON::Field(key: "page")]
  property page : Int32
  @[JSON::Field(key: "fruits")]
  property fruits : Array(String)
end

# First we'll look at encoding basic data types to
# JSON strings. Here are some examples for atomic values.
puts JSON.parse("true")
puts JSON.parse("1")
puts JSON.parse("2.34")
puts JSON.parse("\"crystal\"")

# And here are some for arrays and hashes, which encode
# to JSON arrays and objects as you'd expect.
slc_d = ["apple", "peach", "pear"]
puts slc_d.to_json

map_d = {"apple" => 5, "lettuce" => 7}
puts map_d.to_json

# The JSON module can automatically encode your
# custom data types. It will include all instance
# variables in the encoded output and will by default
# use those names as the JSON keys.
res1_d = Response1.new
res1_d.page = 1
res1_d.fruits = ["apple", "peach", "pear"]
puts res1_d.to_json

# You can use annotations on struct field declarations
# to customize the encoded JSON key names. Check the
# definition of `Response2` above to see an example
# of such annotations.
res2_d = Response2.new
res2_d.page = 1
res2_d.fruits = ["apple", "peach", "pear"]
puts res2_d.to_json

# Now let's look at decoding JSON data into Crystal
# values. Here's an example for a generic data structure.
byt = %({"num":6.13,"strs":["a","b"]})

# We need to parse the JSON string to get a JSON::Any object.
dat = JSON.parse(byt)
puts dat

# In order to use the values in the parsed JSON,
# we'll need to access them using the appropriate methods.
# For example here we get the value of `num` as a Float64.
num = dat["num"].as_f
puts num

# Accessing nested data requires a series of method calls.
strs = dat["strs"].as_a
str1 = strs[0].as_s
puts str1

# We can also decode JSON into custom data types.
# This has the advantages of adding additional
# type-safety to our programs and eliminating the
# need for type casts when accessing the decoded data.
str = %({"page": 1, "fruits": ["apple", "peach"]})
res = Response2.from_json(str)
puts res
puts res.fruits[0]

# In the examples above we always used strings as
# intermediates between the data and JSON representation.
# We can also stream JSON encodings directly to IO objects
# like STDOUT or even HTTP response bodies.
d = {"apple" => 5, "lettuce" => 7}
d.to_json(STDOUT)
puts

To run the program, save it as json_example.cr and use the crystal command:

$ crystal json_example.cr
true
1
2.34
"crystal"
["apple","peach","pear"]
{"apple":5,"lettuce":7}
{"page":1,"fruits":["apple","peach","pear"]}
{"page":1,"fruits":["apple","peach","pear"]}
{"num":6.13,"strs":["a","b"]}
6.13
a
Response2(@fruits=["apple", "peach"], @page=1)
apple
{"apple":5,"lettuce":7}

We’ve covered the basics of JSON in Crystal here, but check out the JSON module documentation for more details.