我正在制作一个通用的列表包装器,其中包含元信息和实际数据(列表)。这是Vert.x项目,由于EventBus的原因,包装程序必须支持序列化到JSON和从JSON反序列化。
简单用法:
Wrapper<SomeType> wrapper = new Wrapper<>(metaInfo, list);
我认为使用Jackson可能是最简单的方法(因为Vert.x在后台也使用Jackson)。
SomeType
可以是我的团队使用的任何类型,可以是生成的POJO或某种库类型(例如Vert.x中的JsonObject
)。这很重要,因为我们无法更改该类型(例如,添加默认构造函数,getter / setter等)或对其进行注释(例如,@JsonProperty
,@JsonCreator
等),仅在需要时编写自定义(反)序列化器
要能够将JSON反序列化为适当的类型,还需要一些其他信息,而对于Jackson,可以通过@JsonTypeInfo
实现。因此,我的(简化此问题)包装器类如下:
class Wrapper<T> {
@JsonProperty("info")
private String someInfo;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "@class")
private List<T> data;
@JsonCreator
public Wrapper(@JsonProperty("info") String someInfo, @JsonProperty("data") List<T> data) {
this.someInfo = someInfo;
this.data = data;
}
public static <T> Wrapper<T> fromJson(String json) {
if (json == null) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
//in test, I've hard coded deserializer for ProblematicClass
module.addDeserializer(ProblematicClass.class, new ProblematicClassDeserializer());
mapper.registerModule(module);
try {
return mapper.readValue(json, Wrapper.class);
} catch (IOException e) {
throw new IllegalArgumentException("Cannot deserialize Wrapper from Json. Message: " + e.getMessage(), e);
}
}
public String toJson() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
//in test, I've hard coded deserializer for ProblematicClass
module.addSerializer(ProblematicClass.class, new ProblematicClassSerializer());
mapper.registerModule(module);
try {
return mapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Cannot serialize Wrapper to Json. Message: " + e.getMessage(), e);
}
}
}
如果类型为“ Jackson可序列化”(例如具有默认构造函数,getter / setter等),则一切正常,但如果不成功,则会出现问题。这是一个有问题的示例类。我将此类编写为Vert.x JsonObject
的测试别名,因此MVCE很小,不需要Vert.x依赖项。
class ProblematicClass {
private Map<String, Object> map;
public ProblematicClass() {
this.map = new HashMap<>();
}
public Boolean isEmpty() {
return map.isEmpty();
}
public Map<String, Object> getMap() {
return map;
}
public ProblematicClass put(String key, String value) {
map.put(key, value);
return this;
}
public ProblematicClass put(String key, Integer value) {
map.put(key, value);
return this;
}
//put (..., boolean), put (..., Float) etc.
public String toJson() throws JsonProcessingException {
//e.g. some internal logic (as for JsonObject)
//this is just example (I know that everything is written as string)
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(map);
}
public static ProblematicClass fromJson(String json) throws IOException {
ProblematicClass instance = new ProblematicClass();
ObjectMapper mapper = new ObjectMapper();
instance.map = (Map<String, Object>) mapper.readValue(json, Map.class);
return instance;
}
//getString, getInteger, getFloat etc.
}
此类显然需要自定义序列化器/反序列化器,因为它具有例如isEmpty
不能反序列化。请记住,我们“无法”更改此类,例如来自外部库。
因此,此代码成功序列化(生成错误的JSON),并在反序列化失败:
List<ProblematicClass> list = new ArrayList<>();
list.add(ProblematicClass.fromJson("{\"a\": \"10\"}"));
list.add(ProblematicClass.fromJson("{\"b\": \"20\"}"));
Wrapper<ProblematicClass> wrapper = new Wrapper<>("info", list);
String json = wrapper.toJson();
产生的JSON看起来像这样:
{"data":[{"@class":"com.test.ProblematicClass","map":{"a":"10"},"empty":false},
{"@class":"com.test.ProblematicClass","map":{"b":"20"},"empty":false}],"info":"info"}
并且它不可反序列化,因为empty
JSON密钥。因此,我们需要编写序列化器和反序列化器:
class ProblematicClassSerializer extends StdSerializer<ProblematicClass> {
public ProblematicClassSerializer() {
this(null);
}
protected ProblematicClassSerializer(Class<ProblematicClass> t) {
super(t);
}
@Override
public void serializeWithType(ProblematicClass value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException {
WritableTypeId typeId = typeSer.typeId(value, START_OBJECT);
typeSer.writeTypePrefix(gen, typeId);
gen.writeFieldName("@json");
serialize(value, gen, serializers); //our custom serialize method written below
typeSer.writeTypeSuffix(gen, typeId);
}
@Override
public void serialize(ProblematicClass value, JsonGenerator jgen, SerializerProvider serializerProvider) throws IOException {
jgen.writeRawValue(value == null ? null : value.toJson());
}
}
class ProblematicClassDeserializer extends StdDeserializer<ProblematicClass> {
public ProblematicClassDeserializer() {
this(null);
}
protected ProblematicClassDeserializer(Class<?> vc) {
super(vc);
}
@Override
public ProblematicClass deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String json = jsonParser.getCodec().readTree(jsonParser).toString(); //this is actual json representation
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = mapper.readValue(json, Map.class); //I know this is redundant, but for example it is fine
return json == null ? null : ProblematicClass.fromJson(mapper.writeValueAsString(map.get("@json")));
//sometimes I got {"key": "value"} //when serialize is called (ProblematicClass wrapped)
//and sometimes I got {"@json": {"key": "value"}} //when using bare ProblematicClass as in Wrapper<ProblematicClass>
//I need to distinguish these two cases but I don't know when is te first and when second
//because deserializationContext is not giving me these information
}
}
据我了解,当serialize
是另一个类的属性(例如,写在下面的ProblematicClass
时,将调用SomeRealClass
,并且得到的JSON将是{"pclass": {"a": 10}}
,这是可以的。因此,我在jgen.writeRawValue
中使用了serialize
。
class SomeRealClass {
public ProblematicClass pclass = ProblematicClass.fromJson("{\"a\": \"10\"}");
public Integer i1 = 10, i2 = 20;
SomeRealClass() throws IOException {
}
}
serializeWithType
是在我单独序列化ProblematicClass
时使用的,然后生成的JSON将是{"a": 10}
,这也是可以的。
但是,由于我在@JsonTypeInfo
中使用了Wrapper
,因此Jackson注入了类型信息("@class": "com.test.ProblematicClass"
),这导致第二个示例({"a": 10}
)出现问题。因此,在serializeWithType
中,我写了gen.writeFieldName("@json");
来“包装” JSON,因此它应该看起来像这样(包含Jackson的元信息):
{"info":"info","data":[{"@class":"com.test.ProblematicClass","@json":{"a":"10"}},
{"@class":"com.test.ProblematicClass","@json":{"b":"20"}}]}
这很好,直到反序列化为止。我无法区分使用的是哪种类型的序列化,一种没有@json
(例如"pclass": {"a": 10}
)还是带有@json
的序列化。在前一种情况下(调用map.get("@json")
时,null
返回serialize
。
说了这么多,上面有详细说明的代码:
public static void main(String[] args) throws IOException {
//without (de)serializes this does not work as it's json is not deserializable (because of 'empty' key)
// {"data":[{"@class":"com.test.ProblematicClass","map":{"a":"10"},"empty":false},
// {"@class":"com.test.ProblematicClass","map":{"b":"20"},"empty":false}],"info":"info"}
//when I hardcode (de)serializer then this (correct) json is produced:
// {"info":"info","data":[{"@class":"com.test.ProblematicClass","@json":{"a":"10"}},
// {"@class":"com.test.ProblematicClass","@json":{"b":"20"}}]}
{
List<ProblematicClass> list = new ArrayList<>();
list.add(ProblematicClass.fromJson("{\"a\": \"10\"}"));
list.add(ProblematicClass.fromJson("{\"b\": \"20\"}"));
Wrapper<ProblematicClass> wrapper = new Wrapper<>("info", list);
String json = wrapper.toJson();
Wrapper<ProblematicClass> copy = Wrapper.fromJson(json);
}
//---------------------------------------------------------
//but, when container class is used (SomeRealClass)
//this @json is redundant, so I wrote it only in serializeWithType
//resulting JSON is correct? there is no @json, like: "problematicClass": {"@json": {"a": "10}}
//and deserialization fails (map.get("@json") is null)
// {"info":"info-real","data":[{"@class":"com.test.SomeRealClass","problematicClass":{"a":"10"},"i1":10,"i2":20},
// {"@class":"com.test.SomeRealClass","problematicClass":{"a":"10"},"i1":10,"i2":20}]}
{
List<SomeRealClass> list = new ArrayList<>();
list.add(new SomeRealClass());
list.add(new SomeRealClass());
Wrapper<SomeRealClass> wrapper = new Wrapper<>("info-real", list);
String json = wrapper.toJson();
Wrapper<SomeRealClass> copy = Wrapper.fromJson(json);
}
}
这是pastebin上可正常使用的MVCE。
关于如何解决此问题的任何建议?但是,请不要建议“总是在包装器中包装类型”,例如class ProblematicClassHolder {public ProblematicClass pclass; }
。
非常感谢您阅读这个冗长的问题,当然也感谢所有评论和答案。