ContextualDeserializer,用于使用 Jackson 将 JSON 映射到不同类型的映射

发布于 2024-11-24 16:50:42 字数 3077 浏览 0 评论 0 原文

此 JSON 片段应映射到一个 Java 对象,其中包含 Map 类型的 cars 字段和 bikes 类型的 bikes 字段输入地图<字符串,自行车>。因为自行车和汽车在 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"
        }
    }
}

我考虑过拥有一个通用自定义反序列化器的实例,该实例可以由基于 BeanProperty 参数的 ContextualDeserializercreateContextual(DeserializationConfig cfg, BeanProperty property) 方法返回。通用自定义反序列化器如下所示:

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
    }
}

下面的上下文序列化器不起作用,因为从 MapsGenericDeserializerJsonDeserializer> 的转换不起作用可能的。也许这在较新版本的 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;
        }
    }

    // ...
}

This JSON snippet should be mapped to a Java-Objects that contains a cars field of type Map<String, Car> and a bikes field of type Map<String, Bike>. Because bikes and cars can be empty strings in the JSON file, i need a custom deserializer (See this question).

{
    "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"
        }
    }
}

I thought about having instances of a generic custom deserializer that can be returned by the createContextual(DeserializationConfig cfg, BeanProperty property) method of a ContextualDeserializer based on the BeanProperty parameter. The generic custom deserializer looks like this:

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
    }
}

The contextual serializer below does not work because casting from MapsGenericDeserializer<Car> to JsonDeserializer<Map<String,?>> is not possible. Maybe this is possible in newer Versions of Java, but it does not work on the Version of Android I am coding for. So how can I implement the desired behaviour?

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;
        }
    }

    // ...
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

稀香 2024-12-01 16:50:42

这是我可能采取的方法。

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));
  }
}

泛型类型参数可能有点乱。我做了一些快速复制粘贴。

Here's how I might approach it.

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));
  }
}

The generic type parameters might be a little out of order. I did a bit of quick copy-pasting.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文