Search code examples
crystal-lang

How to configure JSON.mapping for Array of Array of Strings to become a Hash?


I am trying to process the following JSON that I receive from an API.

{"product":"midprice",
"prices":[
  ["APPLE","217.88"],
  ["GOOGLE","1156.05"],
  ["FACEBOOK","160.58"]
]}

I can get a basic mapping working with:

require "json"

message = "{\"product\":\"midprice\",\"prices\":[[\"APPLE\",\"217.88\"],[\"GOOGLE\",\"1156.05\"],[\"FACEBOOK\",\"160.58\"]]}"

class Midprice
  JSON.mapping(
    product: String,
    prices: Array(Array(String)),
  )
end

midprice = Midprice.from_json(message)
p midprice.product # Outputs the String
p midprice.prices # Outputs 

Crystal 0.26.1 Code: https://play.crystal-lang.org/#/r/515o

But ideally I would like prices to be a hash with the stock name as the key and the price as the value. Can this be done with JSON.mapping?


Solution

  • JSON.mapping is going to be removed in favor of JSON::Serializable and annotations. You can use it like:

    class Midprice
      include JSON::Serializable
    
      getter product : String
    
      @[JSON::Field(converter: StockConverter.new)]
      getter prices : Hash(String, String)
    end
    

    You need to use a converter to modify prices into the format that you want.

    In this case the input is an Array(Array(String)) and the output is a Hash(String, String) which is a different type. You need to implement a custom from_json method for your converter.

    class StockConverter
      def initialize
        @prices = Hash(String, String).new
      end
    
      def from_json(pull : JSON::PullParser)
        pull.read_array do
          pull.read_array do
            stock = pull.read_string
            price = pull.read_string
    
            @prices[stock] = price
          end
        end
    
        @prices
      end
    end
    

    Here's the full working code: https://play.crystal-lang.org/#/r/51d9