views:

2081

answers:

3

In the following Jackson/Java code that serializes objects into JSON, I am getting this:

{"animal":{"x":"x"}}

However, what I actually want to get is this:

{"dog":{"x":"x"}}

Is there something I can do to AnimalContainer so that I get the runtime type ("dog", "cat") of the object, instead of "animal")? (Edit: I am aware that the map name comes from the getter- and setter- method names.) The only way I can think of to do it is within AnimalContainer to have an attribute of each type of Animal, have setters and getters for all of them, and enforce that only one is valued at a time. But this defeats the purpose of having the Animal superclass and just seems wrong. And in my real code I actually have a dozen subclasses, not just "dog" and "cat". Is there a better way to do this (perhaps using annotations somehow)? I need a solution for deserializing, as well.

public class Test
{
   public static void main(String[] args) throws Exception
   {
      AnimalContainer animalContainer = new AnimalContainer();
      animalContainer.setAnimal(new Dog());

      StringWriter sw = new StringWriter();   // serialize
      ObjectMapper mapper = new ObjectMapper(); 
      MappingJsonFactory jsonFactory = new MappingJsonFactory();
      JsonGenerator jsonGenerator = jsonFactory.createJsonGenerator(sw);
      mapper.writeValue(jsonGenerator, animalContainer);
      sw.close();
      System.out.println(sw.getBuffer().toString());
   }
   public static class AnimalContainer
   {
      private Animal animal;
      public Animal getAnimal() {return animal;}
      public void setAnimal(Animal animal) {this.animal = animal;}
   }
   public abstract static class Animal 
   {
      String x = "x";
      public String getX() {return x;}
   }
   public static class Dog extends Animal {}
   public static class Cat extends Animal {} 
}
A: 

This is the only way I can think of to do it, and it is ugly. Is there a better way?

   @JsonWriteNullProperties(false)
   public static class AnimalContainer
   {
      private Animal animal;

      public Animal getCat()
      {
         return animal instanceof Cat ? animal : null;
      }
      public void setCat(Cat cat)
      {
         this.animal = cat;
      }
      public Animal getDog()
      {
         return animal instanceof Dog ? animal : null;
      }
      public void setDog(Dog dog)
      {
         this.animal = dog;
      }
      public Animal getFish()
      {
         return animal instanceof Fish ? animal : null;
      }
      public void setFish(Fish fish)
      {
         this.animal = fish;
      }
   }
seansand
+1  A: 

This is probably not the answer you are looking for, but there are plans to implement proper "polymorphic deserialization" (and necessary support on serialization for it), for Jackson version 1.4 or so (i.e. not the next one, 1.3, but one after that).

For current version, you have to implement custom serializers/deserializers: I would probably just define factory method for deserialization, and type getter for serializer (define 'getAnimalType' or whatever in abstract base class as abstract, override in sub-classes -- or even just implement in base class, output class name of instance class?).

Anyway, just in case it matters, here are underlying problems wrt implementing handling of sub-classes with JSON, and without schema language (since json doesn't really have widely used one):

  • how to separate data (bean property values) from metadata (type information only needed to construct proper subclasses) -- must be kept separate, but JSON as format has no way to define (could use naming convention)
  • how to add proper annotations to generate and use such metadata; and without depending on language specific features (shouldn't have to tie to java class names for example)

These are solvable problems, but not trivially easy to solve. :-)

StaxMan
At least this a confirmation that there's no way to do this within Jackson (yet), that I'm not missing something; it has to be done the hard way.
seansand
Correct -- I can't think of a way to do it easy way (without custom serializer/deserializer) with Jackson 1.2 or before.
StaxMan
FWIW, Jackson version 1.5 will have support for full handling of polymorphism for deserialization (1.4 was just released).
StaxMan
+1  A: 

As per this announement, Jackson 1.5 implements full polymorphic type handling, and trunk now has that code integrated.

There are two simple ways to make this work:

  • Add @JsonTypeInfo annotation in supertype (Animal here), OR
  • Configure object mapper by calling ObjectMapper.enableDefaultTyping() (but if so, Animal needs to be abstract type)
StaxMan