Crystal: JSON.mapping in subclass does not inherit it's parent's mapping

Created on 18 Sep 2016  路  9Comments  路  Source: crystal-lang/crystal

I'm currently building a wrapper for the tumblr api. For this API, there's several post types that all can share common properties - so in this case it makes sense to use inheritance. I currently have a BasePost superclass, with universal properties like id, and then specific classes that inherit from my BasePost class, like a TextPost.

A simplified version of what I'm doing is below -

require "JSON"

class BasePost
  JSON.mapping({
    id: {type: Int32}
  })
end
require "JSON"
require "./base_post"

class TextPost < BasePost
  JSON.mapping({
    name: {type: String}
  })
end

However, upon compilation I receive an error.

this 'initialize' doesn't initialize instance variable '@blog_name' of Tumblr::BasePost, with Tumblr::TextPost < Tumblr::BasePost, rendering it nilable

I assume this means that when I create a TextPost, it doesn't pass the necessary JSON to the BaseClass to fill in non-nilable properties like the id (or in this case, the blog name)

I am told there is no way to currently do this.

I would argue this is not an edge case, as many APIs have a similar layout (like Facebook, Pinterest, Reddit, etc) and copying every BasePost property into my several post types wouldn't be very DRY.

feature discussion stdlib

Most helpful comment

Nor consensus on whether it's liked or not. For me, it would simplify a lot of things in the language.

All 9 comments

One solution:

require "json"

abstract class Post
  macro post_json_mapping(**properties)
    JSON.mapping(
      blog_name: String,
      id: Int64,
      {{properties.stringify[1...-1].id}}
    )
  end
end

class TextPost < Post
  post_json_mapping(
    title: String,
    body: String,
  )
end

text_post = TextPost.from_json(%({"blog_name": "Foo", "id": 123, "title": "hello", "body": "hey"}))
pp text_post

I had to use properties.stringify[1...-1].id because there's no way right now to double-splat inside a macro (you can splat, but it's useless here), so that's an enhancement.

So, in a way, the mapping is repeating in every subclass, and the repetition is simplified via a macro. I have no idea how to implement automatic JSON.mapping inheritance, and using the solution above is kind of OK.

In the future we could have a separate macro to generate from_json/to_json based on a class' instance vars, though in my experience that never works well because later you add more variables that aren't exclusively of the mapped model, or maybe you need converters, etc.

Thanks for the info! When I create the objects, I'm getting the following error -

Invalid memory access (signal 11) at address 0x200000033
[4842533] *CallStack::print_backtrace:Int32 +117
[4765272] __crystal_sigfault_handler +56
[6129368] sigfault_handler +40
Program exited because of a segmentation fault (11)

Do you know why this might be happening? Right now I'm just creating the BasePost types, and not even bothering with the subclasses. Here's the function in question. The only thing that seems different about my implementation is that my base_post has types and keys added, but the error persists even when I make the file look just like your example.

(Also, would you recommend that I close this issue or leave it open? )

I think the mapping config could be stored in a constant in each class, extending when inheriting and calling json_mapping with all the fields. I remeber an issue about reusing the config. (Over mobile sorry for the lack of source code)

By any chance, is this being worked on? I'm building a wrapper for an API currently, and attempting to use subclasses.

No. I'd like to implement the Meta attribute at one point, but there was no consensus on its format.

https://github.com/crystal-lang/crystal/issues/3620

Nor consensus on whether it's liked or not. For me, it would simplify a lot of things in the language.

I'm afraid that most of the discussion in #3620 is over my head, but I'd definitely support something that would fix this.

I think this can be closed as #6063 offers an alternative that can inherit JSON mappings.

Was this page helpful?
0 / 5 - 0 ratings