ContextualDeserializer для отображения JSON на различные типы карт с Джексоном - PullRequest
3 голосов
/ 15 июля 2011

Этот фрагмент JSON должен быть сопоставлен с объектами Java, которые содержат поле cars типа Map<String, Car> и поле bikes типа Map<String, Bike>. Поскольку велосипеды и автомобили могут быть пустыми строками в файле JSON, мне нужен специальный десериализатор ( См. Этот вопрос ).

{
    "id" : "1234",
    "name" : "John Doe",
    "cars" : {
        "Tesla Model S" : {
            "color" : "silver",
            "buying_date" : "2012-06-01"
        },
        "Toyota Yaris" : {
            "color" : "blue",
            "buying_date" : "2005-01-01"
        }
    },
    "bikes" : {
        "Bike 1" : {
            "color" : "black"
        },
        "Bike 2" : {
            "color" : "red"
        }
    }
}

Я думал о наличии экземпляров универсального пользовательского десериализатора, который может быть возвращен методом createContextual(DeserializationConfig cfg, BeanProperty property) ContextualDeserializer на основе параметра BeanProperty. Общий пользовательский десериализатор выглядит так:

public class MapsGenericDeserializer<T> extends
        JsonDeserializer<Map<String, T>> {

    private ObjectMapper mapper; // ObjectMapper without special map deserializer

    public MapsGenericDeserializer(ObjectMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public Map<String, T> deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        ObjectCodec codec = jp.getCodec();
        JsonNode node = codec.readTree(jp);
        if (!"".equals(node.getTextValue())) {
            return mapper.readValue(node, 
                    new TypeReference<Map<String, T>>() {});
        }
        return null; // Node was an empty string
    }
}

Контекстный сериализатор ниже не работает, потому что приведение от MapsGenericDeserializer<Car> к JsonDeserializer<Map<String,?>> невозможно. Возможно, это возможно в более новых версиях Java, но это не работает в той версии Android, для которой я пишу код. Так как же мне реализовать желаемое поведение?

public class MapsDeserializer extends JsonDeserializer<Map<String, ?>>
        implements ContextualDeserializer<Map<String, ?>> {

    private ObjectMapper mapper;

    MapsGenericDeserializer<Car> carDeserializer = new MapsGenericDeserializer<Car>(mapper);
    MapsGenericDeserializer<Bike> bikeDeserializer = new MapsGenericDeserializer<Bike>(mapper);

    public MapsDeserializer(ObjectMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public JsonDeserializer<Map<String, ?>> createContextual(DeserializationConfig cfg,
            BeanProperty property) throws JsonMappingException {

        Class<?> targetClass = property.getType().containedType(1).getRawClass();

        if(targetClass.equals(Car.class) { 
            return carDeserializer; // Type mismatch!
        } else if (targetClass.equals(Bike.class)) {
            return bikeDeserializer; // Type mismatch!
        } else {
            return this;
        }
    }

    // ...
}

1 Ответ

3 голосов
/ 16 июля 2011

Вот как я мог бы подойти к этому.

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

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.BeanProperty;
import org.codehaus.jackson.map.ContextualDeserializer;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.module.SimpleModule;

public class Foo
{
  public static void main(String[] args) throws Exception
  {
    EmptyStringAsMapDeserializer<Map<String, ?>> emptyStringAsMapDeserializer = 
        new EmptyStringAsMapDeserializer<Map<String, ?>>(null, new ObjectMapper());

    SimpleModule module = new SimpleModule("ThingsDeserializer", Version.unknownVersion());
    module.addDeserializer(Map.class, emptyStringAsMapDeserializer);

    ObjectMapper mapper = new ObjectMapper().withModule(module);

    Person person = mapper.readValue(new File("input.json"), Person.class);
    System.out.println(mapper.writeValueAsString(person));
  }
}

class Person
{
  public int id;
  public String name;
  public Map<String, Car> cars;
  public Map<String, Bike> bikes;
}

class Car
{
  public String color;
  public String buying_date;
}

class Bike
{
  public String color;
}

class EmptyStringAsMapDeserializer<T>
    extends JsonDeserializer<Map<String, ?>>
    implements ContextualDeserializer<Map<String, ?>>
{
  private Class<?> targetType;
  private ObjectMapper mapper;

  EmptyStringAsMapDeserializer(Class<?> targetType, ObjectMapper mapper)
  {
    this.targetType = targetType;
    this.mapper = mapper;
  }

  @Override
  public JsonDeserializer<Map<String, ?>> createContextual(DeserializationConfig config, BeanProperty property)
      throws JsonMappingException
  {
    return new EmptyStringAsMapDeserializer<Object>(property.getType().containedType(1).getRawClass(), mapper);
  }

  @Override
  public Map<String, ?> deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException
  {
    JsonNode node = jp.readValueAsTree();
    if ("".equals(node.getTextValue()))
      return new HashMap<String, Object>();
    ObjectMapper mapper = new ObjectMapper();
    return mapper.readValue(node, mapper.getTypeFactory().constructMapType(Map.class, String.class, targetType));
  }
}

Параметры общего типа могут быть немного не в порядке. Я сделал небольшое быстрое копирование.

...