Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to map JSON::Any to custom Object in Crystal language?

Tags:

crystal-lang

How to map parsed JSON as JSON::Any type to custom Object?

In my case, I am working on chat client. Chat API can respond to requests with following JSON:

{"ok" => true,
 "result" =>
  [{"update_id" => 71058322,
    "message" =>
     {"message_id" => 20,
      "from" => "Benjamin",
      "text" => "hey"}}]}

Somewhere inside my API client code I parse these JSON to perform some basic health checks and pass the result to response consumer. In the consumer, I iterate over result array and try to convert each update to appropriate object:

module Types
  class Update
    JSON.mapping({
      update_id: {type: Int32},
      message:   {type: Message},
    })
  end
end

module Types
  class Message
    JSON.mapping({
      message_id: Int32,
      date:       Int32,
      text:       String,
    })
  end
end

return unless response["ok"]
response["result"].each do |data|
  update = Types::Update.from_json(data)
end

Unfortunately, last line results in compile error:

no overload matches 'JSON::Lexer.new' with type JSON::Any

Apparently, Object.from_json can accept only String JSONs, but not parsed JSON. In my case data is JSON::Any object.

Dirty fix Types::Update.from_json(data.to_json) works, but it looks ridiculous.

What is the proper way to map JSON object to custom type preserving all nested structure?

like image 478
vtambourine Avatar asked Dec 29 '17 01:12

vtambourine


1 Answers

JSON.mapping doesn't work nicely together with JSON.parse. To solve your problem you can create another mapping Types::Result and parse a hole json using Object.from_json which is even much more convenient to work with:

module Types
  class Message
    JSON.mapping(
      message_id: Int32,
      text:       String
    )
  end

  class Update
    JSON.mapping(
      update_id: Int32,
      message:   Message
    )
  end

  class Result
    JSON.mapping(
      success: { key: "ok", type: Bool },
      updates: { key: "result", type: Array(Update) }
    )
  end
end

result = Types::Result.from_json string_json
result.success                    # => true
result.updates.first.message.text # => "hey"
like image 168
Vitalii Elenhaupt Avatar answered Sep 23 '22 03:09

Vitalii Elenhaupt