【问题标题】:Value Dependent Deserialization with JacksonJackson 的值依赖反序列化
【发布时间】:2023-01-12 17:08:19
【问题描述】:

我想反序列化为数据结构。根据我想反序列化为同一接口的不同实现的 JSON 数据的版本。到目前为止,这适用于自定义解串器。

但是,在数据结构中我使用引用。和我希望当遇到未定义的引用时会抛出异常。按照我的编程方式,这不能与界面一起使用。

我创建了一个带有(当前未通过)测试用例的小示例来显示所需的行为。

附加信息:在测试用例中,当我在 readValue 中使用具体类(而不是接口)时,会发生所需的行为。也就是说,当我写 mapper.readValue(buggy, Database2.class); 而不是 mapper.readValue(buggy, DatabaseI.class); 时。但是后来我失去了从 JSON 数据的特定内容中抽象出来的能力。

import static org.junit.jupiter.api.Assertions.assertThrows;

import com.btc.adt.pop.scen.objectstreams.Person;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.IntNode;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;


public class Example {

  @Test
  public void test() throws JsonProcessingException {

    ObjectMapper mapper =
        new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);

    SimpleModule module = new SimpleModule();
    module.addDeserializer(DatabaseI.class, new ToyDeserializer());
    mapper.registerModule(module);

    String correct = "{'version':1,'people':[{'id':'a','friends':['b','c']},{'id':'b','friends':['c']},{'id':'c','friends':['b']}]}";
    DatabaseI deserCorrect = mapper.readValue(correct, DatabaseI.class);
    System.out.println(mapper.writeValueAsString(deserCorrect));

    String buggy = "{'version':2,'people':[{'id':'a','friends':['b','c']},{'id':'b','friends':['c']},{'id':'c','friends':['FOO']}]}";
    assertThrows(Exception.class, () -> {
      mapper.readValue(buggy, DatabaseI.class);
    }, "The reference FOO is undefined. An Exception should be thrown.");
  }
}

interface DatabaseI {

}

class Database1 implements DatabaseI {

  private int version;
  private List<Person> people = new ArrayList<>();

  public Database1() {
  }

  public List<Person> getPeople() {
    return people;
  }

  public void setPeople(List<Person> people) {
    this.people = people;
  }

  public int getVersion() {
    return version;
  }

  public void setVersion(int version) {
    this.version = version;
  }
}

class Database2 implements DatabaseI {

  private String version;
  private List<Person> people = new ArrayList<>();

  public Database2() {
  }

  public List<Person> getPeople() {
    return people;
  }

  public void setPeople(List<Person> people) {
    this.people = people;
  }

  public String getVersion() {
    return version;
  }

  public void setVersion(String version) {
    this.version = version;
  }
}

class ToyDeserializer extends StdDeserializer<DatabaseI> {

  protected ToyDeserializer(Class<?> vc) {
    super(vc);
  }

  public ToyDeserializer() {
    this(null);
  }

  @Override
  public DatabaseI deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JacksonException {
    ObjectMapper mapper = (ObjectMapper) jp.getCodec();
    JsonNode node = mapper.readTree(jp);

    int version = (Integer) ((IntNode) node.get("version")).numberValue();
    if (version == 1) {
      return mapper.treeToValue(node, Database1.class);
    } else {
      return mapper.treeToValue(node, Database2.class);
    }
  }
}

【问题讨论】:

    标签: jackson json-deserialization


    【解决方案1】:

    这个问题很好!如果您想了解为什么没有抛出异常,您的类 Person 必须如下所示:

    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "id",
            scope = Person.class,
            resolver = SimpleObjectIdResolverThrowsException.class
    )
    @JsonIdentityReference
    class Person {
    
        String id;
        List<Person> friends = new ArrayList<>();
    
        @ConstructorProperties({"id"})
        public Person(String id) {
            this.id = id;
        }
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public List<Person> getFriends() {
            return friends;
        }
    
        public void setFriends(List<Person> friends) {
            this.friends = friends;
        }
    }
    
    class SimpleObjectIdResolverThrowsException extends SimpleObjectIdResolver {
    
        public SimpleObjectIdResolverThrowsException() {
            super();
        }
    
        @Override
        public Object resolveId(ObjectIdGenerator.IdKey id) {
            if (this._items == null) {
                return null;
            }
    
            Object obj = this._items.get(id);
            if (obj == null) {
                throw new RuntimeException("Unresolved reference for: " + id);
            }
    
            return obj;
        }
    
        @Override
        public ObjectIdResolver newForDeserialization(Object context) {
            return new SimpleObjectIdResolverThrowsException();
        }
    }
    

    现在你可以在方法resolveId中设置断点,看看当我们反序列化字符串"{'version':1,'people':[{'id':'a','friends':['b','c']},{'id':'b','friends':['c']},{'id':'c','friends':['b']}]}"时会发生什么:

    问题是对象一个接一个地被处理,而来自朋友列表的引用在那个时候没有被解析。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多