Jackson deserialization of type with different objects

前端 未结 4 1479
清酒与你
清酒与你 2020-12-06 02:21

I have a result from a web service that returns either a boolean value or a singleton map, e.g.

Boolean result:

{
    id: 24428,
    rated: false
}
<         


        
相关标签:
4条回答
  • 2020-12-06 02:44

    I asked a similar question - JSON POJO consumer of polymorphic objects

    You have to write your own deserialiser that gets a look-in during the deserialise process and decides what to do depending on the data.

    There may be other easier methods but this method worked well for me.

    0 讨论(0)
  • 2020-12-06 02:47

    You have to write your own deserializer. It could look like this:

    @SuppressWarnings("unchecked")
    class RatingJsonDeserializer extends JsonDeserializer<Rating> {
    
        @Override
        public Rating deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            Map<String, Object> map = jp.readValueAs(Map.class);
    
            Rating rating = new Rating();
            rating.setId(getInt(map, "id"));
            rating.setRated(getRated(map));
    
            return rating;
        }
    
        private int getInt(Map<String, Object> map, String propertyName) {
            Object object = map.get(propertyName);
    
            if (object instanceof Number) {
                return ((Number) object).intValue();
            }
    
            return 0;
        }
    
        private int getRated(Map<String, Object> map) {
            Object object = map.get("rated");
            if (object instanceof Boolean) {
                if (((Boolean) object).booleanValue()) {
                    return 0; // or throw exception
                }
    
                return -1;
            }
    
            if (object instanceof Map) {
                return getInt(((Map<String, Object>) object), "value");
            }
    
            return 0;
        }
    }
    

    Now you have to tell Jackson to use this deserializer for Rating class:

    @JsonDeserialize(using = RatingJsonDeserializer.class)
    class Rating {
    ...
    }
    

    Simple usage:

    ObjectMapper objectMapper = new ObjectMapper();
    System.out.println(objectMapper.readValue(json, Rating.class));
    

    Above program prints:

    Rating [id=78, rated=10]
    

    for JSON:

    {
        "id": 78,
        "rated": {
            "value": 10
        }
    }
    

    and prints:

    Rating [id=78, rated=-1]
    

    for JSON:

    {
        "id": 78,
        "rated": false
    }
    
    0 讨论(0)
  • 2020-12-06 02:49

    I found a nice article on the subject: http://programmerbruce.blogspot.com/2011/05/deserialize-json-with-jackson-into.html

    I think that the approach of parsing into object, is possibly problematic, because when you send it, you send a string. I am not sure it is an actual issue, but it sounds like some possible unexpected behavior. example 5 and 6 show that you can use inheritance for this.

    Example:

    Example 6: Simple Deserialization Without Type Element To Container Object With Polymorphic Collection

    Some real-world JSON APIs have polymorphic type members, but don't include type elements (unlike the JSON in the previous examples). Deserializing such sources into polymorphic collections is a bit more involved. Following is one relatively simple solution. (This example includes subsequent serialization of the deserialized Java structure back to input JSON, but the serialization is relatively uninteresting.)

    // input and output:
    //   {
    //     "animals":
    //     [
    //       {"name":"Spike","breed":"mutt","leash_color":"red"},
    //       {"name":"Fluffy","favorite_toy":"spider ring"},
    //       {"name":"Baldy","wing_span":"6 feet",
    //           "preferred_food":"wild salmon"}
    //     ]
    //   }
    
    import java.io.File;
    import java.io.IOException;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.Map.Entry;
    
    import org.codehaus.jackson.JsonNode;
    import org.codehaus.jackson.JsonParser;
    import org.codehaus.jackson.JsonProcessingException;
    import org.codehaus.jackson.Version;
    import org.codehaus.jackson.map.DeserializationContext;
    import org.codehaus.jackson.map.ObjectMapper;
    import org.codehaus.jackson.map.deser.StdDeserializer;
    import org.codehaus.jackson.map.module.SimpleModule;
    import org.codehaus.jackson.node.ObjectNode;
    
    import fubar.CamelCaseNamingStrategy;
    
    public class Foo
    {
      public static void main(String[] args) throws Exception
      {
        AnimalDeserializer deserializer = 
            new AnimalDeserializer();
        deserializer.registerAnimal("leash_color", Dog.class);
        deserializer.registerAnimal("favorite_toy", Cat.class);
        deserializer.registerAnimal("wing_span", Bird.class);
        SimpleModule module =
          new SimpleModule("PolymorphicAnimalDeserializerModule",
              new Version(1, 0, 0, null));
        module.addDeserializer(Animal.class, deserializer);
        
        ObjectMapper mapper = new ObjectMapper();
        mapper.setPropertyNamingStrategy(
            new CamelCaseNamingStrategy());
        mapper.registerModule(module);
    
        Zoo zoo = 
            mapper.readValue(new File("input_6.json"), Zoo.class);
        System.out.println(mapper.writeValueAsString(zoo));
      }
    }
    
    class AnimalDeserializer extends StdDeserializer<Animal>
    {
      private Map<String, Class<? extends Animal>> registry =
          new HashMap<String, Class<? extends Animal>>();
    
      AnimalDeserializer()
      {
        super(Animal.class);
      }
    
      void registerAnimal(String uniqueAttribute,
          Class<? extends Animal> animalClass)
      {
        registry.put(uniqueAttribute, animalClass);
      }
    
      @Override
      public Animal deserialize(
          JsonParser jp, DeserializationContext ctxt) 
          throws IOException, JsonProcessingException
      {
        ObjectMapper mapper = (ObjectMapper) jp.getCodec();
        ObjectNode root = (ObjectNode) mapper.readTree(jp);
        Class<? extends Animal> animalClass = null;
        Iterator<Entry<String, JsonNode>> elementsIterator = 
            root.getFields();
        while (elementsIterator.hasNext())
        {
          Entry<String, JsonNode> element=elementsIterator.next();
          String name = element.getKey();
          if (registry.containsKey(name))
          {
            animalClass = registry.get(name);
            break;
          }
        }
        if (animalClass == null) return null;
        return mapper.readValue(root, animalClass);
      }
    }
    
    class Zoo
    {
      public Collection<Animal> animals;
    }
    
    abstract class Animal
    {
      public String name;
    }
    
    class Dog extends Animal
    {
      public String breed;
      public String leashColor;
    }
    
    class Cat extends Animal
    {
      public String favoriteToy;
    }
    
    class Bird extends Animal
    {
      public String wingSpan;
      public String preferredFood;
    }
    
    0 讨论(0)
  • 2020-12-06 03:01

    No no no. You do NOT have to write a custom deserializer. Just use "untyped" mapping first:

    public class Response {
      public long id;
      public Object rated;
    }
    // OR
    public class Response {
      public long id;
      public JsonNode rated;
    }
    Response r = mapper.readValue(source, Response.class);
    

    which gives value of Boolean or java.util.Map for "rated" (with first approach); or a JsonNode in second case.

    From that, you can either access data as is, or, perhaps more interestingly, convert to actual value:

    if (r.rated instanced Boolean) {
        // handle that
    } else {
        ActualRated actual = mapper.convertValue(r.rated, ActualRated.class);
    }
    // or, if you used JsonNode, use "mapper.treeToValue(ActualRated.class)
    

    There are other kinds of approaches too -- using creator "ActualRated(boolean)", to let instance constructed either from POJO, or from scalar. But I think above should work.

    0 讨论(0)
提交回复
热议问题