amnaredo / test

0 stars 0 forks source link

Renaming the `$type` attribute name #276

Open amnaredo opened 2 years ago

amnaredo commented 2 years ago

Not sure where to ask an i-dont-know-how-to-do-it question, so I'm doing it here. Feel free to close if necessary.

I have a JSON file in this form:

    "a": {
        "__class__": "ClassA",
        "field": 123
    },
    "b": {
        "__class__": "ClassB",
        "sth", [1,2,3]
    }

In order to parse it, I'd like to create case classes for each of the __class__ defined in this JSON, and use upickle to automatically deserialize it to a class tree. In order to do this, I'd figured that the tagged read function should help. But upickle expects the class name to be named $type instead of __class__. So my question is:

Is it possible to rename $type to something else without modifying the library code? The source code mentions that I need to override the annotate method somewhere, but TBH I don't really understand what's happening inside the library, and can't find the proper way to use this advice.

I understand that I can simply rename all __class__ substrings to $type, but I'd like to know if I can make upickle to use a different tag name.

Thanks for any info on this. ID: 321 Original Author: antekone

amnaredo commented 2 years ago

Okay, I've found the solution.

First I need to create my own object that inherits from upickle.AttributeTagged, and this object needs to override tagName:

object MyDeserializer extends upickle.AttributeTagged {
  override def tagName = "__class__"        
}                                           

Then, all deserializing macros should be called on MyDeserializer object instead of upickle.default, example:

import MyDeserializer._

object Ability {                                 
  implicit val r: Reader[Ability] = Reader.merge(
    PolishableAbility.r,                   // example type 1
    BuildingPlacementAbility.r,            // example type 2
  )                                              
}                                                

// example type 1
@upickle.implicits.key("PolishableAbility")         
case class PolishableAbility(                       
) extends Ability                                   

object PolishableAbility {                          
  implicit val r: Reader[PolishableAbility] = macroR
}                                                   

// example type 2
@upickle.implicits.key("BuildingPlacementAbility")         
case class BuildingPlacementAbility(                       
  val gridId: String                                       
) extends Ability                                          

object BuildingPlacementAbility {                          
  implicit val r: Reader[BuildingPlacementAbility] = macroR
}                                                          

(this macroR is from MyDeserializer.macroR, not from upickle.default.macroR).

This code can be used to deserialize the actual JSON:

val x = MyDeserializer.read[Ability]("""{"__class__": "BuildingPlacementAbility", "gridId": "abc"}""")
println(x) // prints "BuildingPlacementAbility(abc)", like it should

it seems to work. Original Author: antekone