Creeating struct from JSON

I was hoping that by requiring json I’ll get a working “from_json” added to my struct, just as it happened to my NamedTuples, but this code does not work:

require "json"

struct Person
  property name : String
  property email : String
  def initialize(@name, @email)
  end
end


foo = Person.new("Foo", "me@foo.bar")
p! foo

json_str = %{{"name": "Bar", "email": "bar@foobar.com"}}
bar = Person.from_json(json_str)
p! bar

The output is

Showing last frame. Use --error-trace for full trace.

In /usr/share/crystal/src/json/from_json.cr:13:3

 13 | new parser
      ^--
Error: wrong number of arguments for 'Person.new' (given 1, expected 2)

Overloads are:
 - Person.new(name, email)

Apparently the from_json will call the initialize method with a JSON::PullParser object.

I could use that to implement parsing of the JSON, but I wonder, is this the recommended way to do it?

require "json"

struct Person
  property name : String
  property email : String
  def initialize(@name, @email)
  end
  def initialize(pull)
    @name = ""
    @email = ""
    pull.read_object do |key|
      case key
      when "name"
        @name = pull.read_string
      when "email"
        @email = pull.read_string
      end
    end
  end
end


foo = Person.new("Foo", "me@foo.bar")
p! foo

json_str = %{{"name": "Bar", "email": "bar@foobar.com"}}
bar = Person.from_json(json_str)
p! bar

You need to include JSON::Serializable in your type.

I proposed having all types be mapped to JSON/YAML automatically a long time ago: unfortunately it wasn’t well received so you have to write a bit more boilerplate.

2 Likes

Oh, I already had that with a class, but forgotten about it. Thanks. It works now.

I just hit the same issue. I tried to implement some API with NamedTuple.

But Crystal doesn’t have deep auto cast so it didn’t worked well. And also everyone advised to use record.

I tried record just to discover it doesn’t work with from_json and after searching with Google why it is so I found this issue.

Would be much better if 1) Crystal had deep auto cast and 2) from_json worked out of the box.

It deff does if you add include JSON::Serializable.

Related: https://github.com/crystal-lang/crystal/issues/6309

EDIT: To be clear no one is suggesting using a class, but record or struct where a record is an easier way to make immutable structs. NamedTuples are not meant for this as others have mentioned in that reddit thread.

Yes, after finding this topic I realised that. What I meant that it would be better if it just worked out of the box.

I just had to add around 15 do; include JSON::Serializable; end statements to my codebase. It’s a pure boilerplate, with zero meaning, reminding good old Java Enterprise time…

1 Like

:man_shrugging: I mean it’s not really a big deal. It makes it clear that that type should be JSON serializable. Maybe it’s time to revive that issue then.