Crystal comes with a JSON::Serializable module that makes converting raw JSON in to composable objects much easier. This makes validating your JSON, and storing the data nicer and type-safe.
We will use this JSON object for an example:
{
"pay_date": "2021-02-15 10:00:00",
"issue_date": "2021-01-31 14:00:00",
"lines": [
{"text": "Item One", "unit": "USD", "amount": 5500, "tax": 3.8},
{"text": "Item Two", "unit": "USD", "amount": 1032, "tax": 3.8},
]
}
The params
object gives us access to this JSON with params.body
. We can also access individual values with params.from_json["the_key"]
.
class Api::Invoices::Create < ApiAction
post "/api/customers/:customer_id/invoices" do
pay_date = Time.parse(params.from_json["pay_date"].as_s, "%Y-%m-%d %H:%M:%S", Time::Location::UTC)
SaveInvoice.create(pay_date: pay_date) do |op, invoice|
# ...
end
end
end
To simplify the code a bit, we can create separate JSON serializable objects.
class SerializedInvoice
include JSON::Serializable
property pay_date : Time
property issue_date : Time
property lines : Array(SerializedInvoiceLine)
end
class SerializedInvoiceLine
include JSON::Serializable
property text : String
property unit : String
property amount : Int32
property tax : Int32
end
With these two classes, we can use params.body
to clean up our action!
class Api::Invoices::Create < ApiAction
post "/api/customers/:customer_id/invoices" do
serialized_invoice = SerializedInvoice.from_json(params.body)
SaveInvoice.create(serialized_invoice: serialized_invoice) do |op, invoice|
# ...
end
end
end
We will have to tell the SaveInvoice
operation how to handle the serialized object.
class SaveInvoice < Invoice::SaveOperation
needs serialized_invoice : SerializedInvoice
before_save do
pay_date.value = serialized_invoice.pay_date
issue_date.value = serialized_invoice.issue_date
# ...
end
end