使用Jackson实现值相关反序列化

hs1ihplo  于 2023-02-19  发布在  其他
关注(0)|答案(1)|浏览(122)

我想反序列化为一个数据结构,根据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.");
  }
}

class Person {

  @JsonProperty("id")
  private String id;

  @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
      property = "id")
  @JsonIdentityReference(alwaysAsId = true)
  private List<Person> friends = new ArrayList<>();

  public Person() {
  }

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

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

vuktfyat1#

这个问题问得很好!如果你想知道为什么没有抛出异常,你的类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']}]}"时会发生什么:

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

相关问题