Crystal: JSON.Mapping and optional NamedTuples

Created on 24 Jul 2018  路  8Comments  路  Source: crystal-lang/crystal

Hi all,

For Crystal 0.25.1, I have a case where I have a Mapping as follows:

JSON.mapping(
  items: Array({
    "id": Int64,
    "active": Bool,
    ...
  })
)

I use rest call to bring in JSON data from a URL. The "active" is optional in some cases. I tried adding Bool? to see if that will help. However, the from_json.cr code is ending up throwing an exception.

Work around I had was to have a | for each of the optional named tuple fields:

 JSON.mapping(
  items: Array({
    "id": Int64,
    "active": Bool,
    ...
  } | {
    "id": Int64,
     ...   # Without the "active" NamedTuple
  })
)

Now this can lead to some rather unpleasant and hard-to maintain code depending on number of optional fields. What would be the right way to handle or declare these? One thought is to perhaps have "Bool?" as a type to indicate that the exception noted above doesn't get thrown. Thoughts?

Most helpful comment

@whizdom It's not an issue, @ysbaddaden answer is the correct one (use an Item type). Also, please start using JSON::Serializable instead of JSON.mapping. The latter will be eventually removed from the language.

All 8 comments

Mapping options allow you to declare a field as nilable or define a default value. See API docs for specifications.

Sorry, forgot to mention that I tried that:

"active":  { type: Bool, nilable: true },

Received the following compiler error:

Syntax error in src/response/category_response.cr:13: expecting token 'CONST', not 'true'
    "active": { type: Bool, nilable: true },

@straight-shoota Bool? should work too.

Tried Bool? too, still errors out if missing value. For what its worth, I think its something to do with the fact that the mapping is inside the Array({ NamedTuple }). Here's a simple test code to show what I mean:

require "json"

# This class shows the case I'm trying to solve - Array of NamedTuples...
class Test
  JSON.mapping(
    items: Array({
      "active": Bool
      ## Here, "active": Bool? does not work, 
      ## "active": { type: Bool, nilable: true } doesn't work
      ## tried a few other [nonsensicle] things like type set to Bool | Nil.  No luck.
    })
  );
end

# This class works with nilable: true or Bool?  since its straight up attributes
class Test2
  JSON.mapping(
    "active": { type: Bool, nilable: true }
  );
end

# Sample outputs (both output true) but note how the fact that structures are different.
puts Test.from_json(%({"items":[{"active": true}]})).items[0][:active]
puts Test2.from_json(%({"active": true})).active

This doesn't work. JSON.mapping expects a list of field names and types/options. Mappings can't be nested. You either need an additional type or a converter for your array.

@Sija You're right, Bool? should already work if properly used.

You need an Item type with its own mapping and then declare items: Array(Item) in you main type.

Note: this is a question for stackoverflow, not a language issue.

Understood @ysbaddaden . I was debating whether this was an issue or not else would have asked directly on Stackoverflow. Will start there going forward. Thanks @straight-shoota @Sija @ysbaddaden

@whizdom It's not an issue, @ysbaddaden answer is the correct one (use an Item type). Also, please start using JSON::Serializable instead of JSON.mapping. The latter will be eventually removed from the language.

Was this page helpful?
0 / 5 - 0 ratings