How to serialize a struct from any type?

let’s say I have a struct with the ability to take a Hash and assign it’s properties accordingly.
I will provide an example with rethinkDB but really it can happen with every database library out there.

When querying the database the result of the query is of a type called RethinkDB::QueryResult which is really just an alias for many primitives available in Crystal.
On the other hand I got a struct which looks like:

abstract struct Cybergarden::Items::Server
  include JSON::Serializable
  property capacity
  property processors
  property price # money per second
  property maintability    

  def analyze_mps()
      mps = 0
      processor.each {|cpu| mps+=cpu.mps}

  def as_h()
          capacity: @capacity,
          processors: &.as_h,
          price: @price,
          maintability: @maintability

  def from_h(payload : Hash(String, String | Array(Hash(String, String | Int32) | Int32)))
      @capacity = payload["capacity"]
      @processors = payload["processors"].map Cybergarden::Items::CpuTypes[&["type"]].new().from_h()
      @price = payload["price"]
      @maintability = payload["maintabiltiy"]

so when querying the database to find the servers a user own I would like to make a new struct instance out of the available data, the only issue is, the data is RethinkDB::QueryResult and when calling .as_h on it it’s still Hash(String, RethinkDB::QueryResult) and I still won’t be able to do something like

server =

since it doesn’t expect a QueryResult but more specific Hash(String, String | Array(Hash(String, String | Int32) | Int32))
Someone has an idea on how I can implement such stuff? without doing the obvious of making all the property types to be Rethink::QueryResult
Thank you.

Have you considered to simply omit the type restriction for payload?
I assume the method implementation would work with both QueryResult and Hash if it’s just using duck typing?

Hi - There are 2 things you could get back from a query:

  • QueryResult
  • Cursor

QueryResult is a single item whereas Cursor is many items. This is how you could handle each case:

given a container class such as User:

class User
  JSON.mapping(id: String, email: String, name: String)

and a find method:

class UserRepo
  def initialize(@connection : RethinkDB::Connection) ; end

   # the important thing here is the use of `raw`
  def find_by_id(id) : User | Nil
    result = r.table("users").get(id).run(@connection)
    result.raw.nil? ? Nil : User.from_json(result.raw.to_json)   

  # the important thing here is the use of `to_a` and `map`
  def all : Array(User) | Nil
    query = r.table("users").run(@connection)
   case query
   when RethinkDB::Cursor{ |u| User.from_json(u.raw.to_json)  }

Personally I usually return a container from the db queries using the CRZ functional library:

def find_by_id(id) : Result(User, SystemError)

and then when I use it later:

Result.match user_repo.find_by_id(id), {
  [Ok, user]  => {user: user}.to_json,
  [Err, error] => {error: handle_error(error)}.to_json


Using this patterns I can send back on the API the user or the error (after categorising the error and adding an appropriate message for users to see)

Hope that helps

Can’t you create the objects without the intermediate json? It seems like some kind of mapping would be handy.