How to do 'from_json' with Symbol variables?

(a) How do I to do ‘from_json’ on a class that has a property of type Symbol?

e.g.:

require "json"

class Foo
  include JSON::Serializable

  property state : Symbol

  def initialize(@state)
  end
end

f = Foo.new(state: :bar)
p! f # => #<Foo:0x7fbff4141eb0 @state=:bar>
p! f.to_json # => "{\"state\":\"bar\"}"
p! Foo.from_json(f.to_json) # => causes "Error: undefined method 'new' for Symbol.class"

(b) Should a Symbol loose or keep the : when converted to JSON? (I’m not sure myself, so I figured I’d ask.)
e.g.: The f.to_json produces "{\"state\":\"bar\"}", but should it produce "{\"state\":\":bar\"}"?

See: Carcin

You can’t dynamically create a Symbol, so there’s not really a way to do this. Have you considered using an Enum? E.g. something like:

enum State
  FOO
  BAR
end

class Foo
  include JSON::Serializable
 
  property state : State
 
  def initialize(@state : State)
  end
end

f = Foo.new(state: :bar)
p! f # => #<Foo:0x7f124d85eeb0 @state=BAR>
p! f.to_json # => "{\"state\":\"bar\"}"
p! Foo.from_json(f.to_json) # => #<Foo:0x7f124d85ee90 @state=BAR>

It should definitely lose it. JSON doesn’t have the concept of a Symbol. The closest representation is String, hence why it loses the :.

Thanks @Blacksmoke16 . Bummer!

I was wanting to be able to:
(a) import and export a JSON version of the class objects
(b) use an array of Symbols in a class, which the app could add to or remove from this array

So a hard-coded Enum won’t work. I could use Strings instead of Symbols; not as efficient, but probably the best way to be able to do an array of items and still be able to import/export JSON.

Hum; I could ‘recommend’ that the end-user uses Constants for the states/strings; that will help optimize a bit. :slight_smile:

What do these symbols represent? If the symbol values are known ahead of time, but there are multiple of them, sounds like a good usecase for Enums - Crystal.

Hum; maybe so. I’ll have to think about that.

Thanks @Blacksmoke16 , I think Enum’s should work! :slight_smile:

require "json"
class Foo(T)
  include JSON::Serializable
 
  property states_allowed : Array(T) = T.values
  property state : T
  
  def initialize(@state : T)
  end
end
 
f = Foo(Color).new(state: Color::Red)
p! Foo(Color).from_json(f.to_json)

See: Carcin

:+1:. Do you really need the array of allowed states tho? Given the compiler won’t allow you to use an enum member that doesn’t exist.

EDIT: Or is it mainly just for the JSON version of the type?

Do you really need the array of allowed states tho?

I was previously validating that the state requested was in the list of states_allowed. But, due to the nature of Enum’s and the fact that I can get the values, I probably won’t need to specifically note some sort of states_allowed! Plus, the Enum’s methods of .from_value?(value : Int) : self? and .parse?(string : String) : self? will come in very handy! :slight_smile:

Now, if I could just get a Proc to be to/from-JSON-compatible! :laughing:

Basically, I’m working on a fix or alternative for drhuffman12/enable_json_serializable by drhuffman12 · Pull Request #8 · veelenga/aasm.cr · GitHub and Please make AASM compatible with JSON::Serializable · Issue #7 · veelenga/aasm.cr · GitHub … into GitHub - drhuffman12/smcr: State Machine for Crystal for now.

FWIW this isn’t how you implement JSON::Serializable. The main benefit of JSON::Serializable is that it automatically defines how to (de)serialize the type based on the ivars within it. If all you want to do is manually define a way to serialize the type, just define a def to_json(builder : JSON::Builder) : Nil method and call it a day.

1 Like

Hit the same issue. Have a property of Hash(Symbol, String) that fails with the exception below during SomeClass.from_json. I’m ignoring the field with @[JSON::Field(ignore: true)] at the moment.

I actually thought this was a bug until I read above that Symbols can’t be created at runtime in which case I think it would be nice to add this to the documentation (JSON::Serializable - Crystal 1.0.0) and/or the compiler error message rather than say undefined method along with a recommended solution.

In /usr/local/Cellar/crystal/1.0.0_1/src/json/from_json.cr:189:20

 189 | parsed_key = K.from_json_object_key?(key)
                      ^--------------------
Error: undefined method 'from_json_object_key?' for Symbol.class
1 Like