jackson-module-scala icon indicating copy to clipboard operation
jackson-module-scala copied to clipboard

Nested sequence of polymorphic type serialized without type info (ver 2.10.2)

Open tremaluca opened this issue 5 years ago • 4 comments

The following code works as expected

import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo}
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(Array(
  new JsonSubTypes.Type(value = classOf[Ingredient.Meat], name = "meat"),
  new JsonSubTypes.Type(value = classOf[Ingredient.Vegetable], name = "veggie")
))
trait Ingredient
object Ingredient {
  case class Meat(of: String) extends Ingredient
  case class Vegetable(name: String) extends Ingredient
}

case class Burrito(filling: Seq[Ingredient])

case class BurritoTray(content: Seq[Burrito])

object Main extends App {
  val mapper = new ObjectMapper().registerModule(DefaultScalaModule)
  println(mapper.writeValueAsString(BurritoTray(Seq(
    Burrito(Seq(Ingredient.Meat("chicken"), Ingredient.Vegetable("salad"))),
    Burrito(Seq(Ingredient.Vegetable("salad"), Ingredient.Vegetable("pickles")))
  ))))
}

printing the following output

{"content":[{"filling":[{"type":"meat","of":"chicken"},{"type":"veggie","name":"salad"}]},{"filling":[{"type":"veggie","name":"salad"},{"type":"veggie","name":"pickles"}]}]}

But if the Burrito class is replaced by a simple Seq[Ingredient]

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(Array(
  new JsonSubTypes.Type(value = classOf[Ingredient.Meat], name = "meat"),
  new JsonSubTypes.Type(value = classOf[Ingredient.Vegetable], name = "veggie")
))
trait Ingredient
object Ingredient {
  case class Meat(of: String) extends Ingredient
  case class Vegetable(name: String) extends Ingredient
}

case class BurritoTray(content: Seq[Seq[Ingredient]])

object Main extends App {
  val mapper = new ObjectMapper().registerModule(DefaultScalaModule)
  println(mapper.writeValueAsString(BurritoTray(Seq(
    Seq(Ingredient.Meat("chicken"), Ingredient.Vegetable("salad")),
    Seq(Ingredient.Vegetable("salad"), Ingredient.Vegetable("pickles"))
  ))))
}

the type information is omitted in the resulting json

{"content":[[{"of":"chicken"},{"name":"salad"}],[{"name":"salad"},{"name":"pickles"}]]}

This issue is not present in equivalent java code

public class JacksonNestedCollection {
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
    @JsonSubTypes({
        @JsonSubTypes.Type(value = Meat.class, name = "Meat"),
        @JsonSubTypes.Type(value = Veggie.class, name = "Veggie")
    })
    private interface Ingredient {}
    private static class Meat implements Ingredient {
        public final String of;
        public Meat(String of) {
            this.of = of;
        }
    }
    private static class Veggie implements Ingredient {
        public final String name;
        public Veggie(String name) {
            this.name = name;
        }
    }

    private static final class BurritoTray {
        public final List<List<Ingredient>> content;
        public BurritoTray(List<List<Ingredient>> content) {
            this.content = content;
        }
    }

    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        System.out.println(mapper.writeValueAsString(new BurritoTray(Arrays.asList(
            Arrays.asList(new Meat("chicken"), new Veggie("salad")),
            Arrays.<Ingredient>asList(new Veggie("salad"), new Veggie("pickles"))
        ))));
    }
}

tremaluca avatar Mar 09 '20 10:03 tremaluca

I think similar symptom also occurs to Tuple.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
class PyContextMetaType

case class Wpbp (sepInd: Int
                 , esgGrping: Int
                 , psaGrping: Int
                 , pernr: BigInt
                 , eeGrp: Char
                 , eeSubGrp: String
                 , perArea: String
                 , perSubArea: String
                 , orgUnit: String
                 , position: String
                 , costCenter: String) extends PyContextMetaType

case class LogNode2(var text: String,

                   @JsonBackReference
                   dadNode: Option[AnyRef] = None,

                   var data: (Option[PyContextMetaType],Option[PyContextMetaType]) = (None, None),

                   @JsonManagedReference
                   children: ListBuffer[LogNode] = new ListBuffer[LogNode],
                   detailText: String = "") extends CborSerializable
{
    ....
}

object Main3 {
  def main(args: Array[String]): Unit = {
    val wpbp = Wpbp(1,3,3,28000001,1,"A0","CN01","SH01","HR Dept","","CC1234")
    val wpbpNode2 = LogNode2("WPBP", None, (None, Some(wpbp)))

    val mapper = new ObjectMapper() with ScalaObjectMapper
    mapper.registerModule(DefaultScalaModule)

    val jsonStr = mapper.writeValueAsString(wpbpNode2)

    println(jsonStr)
  }

}

Pay attention to the "data" member of LogNode2, which is a Tuple2 that uses PyContextMetaType. The serialized jsonStr lost the type info of Wpbp object:

{
  "text": "WPBP",
  "data": [
    null,
    {
      "sepInd": 1,
      "esgGrping": 3,
      "psaGrping": 3,
      "pernr": 28000001,
      "eeGrp": "\u0001",
      "eeSubGrp": "A0",
      "perArea": "CN01",
      "perSubArea": "SH01",
      "orgUnit": "HR Dept",
      "position": "",
      "costCenter": "CC1234"
    }
  ],
  "children": [],
  "detailText": ""
}

The expected jsonStr should be:

{
  "text": "WPBP",
  "data": [
    null,
    {
      "Wpbp": {
        "sepInd": 1,
        "esgGrping": 3,
        "psaGrping": 3,
        "pernr": 28000001,
        "eeGrp": "\u0001",
        "eeSubGrp": "A0",
        "perArea": "CN01",
        "perSubArea": "SH01",
        "orgUnit": "HR Dept",
        "position": "",
        "costCenter": "CC1234"
      }
    }
  ],
  "children": [],
  "detailText": ""
}

iLoveZod13 avatar Apr 26 '20 10:04 iLoveZod13

@tremaluca

Hi there,

Were you able to find a workaround for this?

iLoveZod13 avatar Apr 26 '20 11:04 iLoveZod13

Hi @iLoveZod13, I also had some different issues with tuples (ids not properly serialized), and I solved by not using tuples, replacing them with a simple "Pair" case class...

tremaluca avatar Apr 26 '20 11:04 tremaluca

Hi @tremaluca Yes I noticed your Pair case class workaround in issue 443, and I am trying that now. Thanks a lot for the enlightening.

iLoveZod13 avatar Apr 26 '20 11:04 iLoveZod13