我想要序列化一个对象,使得其中一个字段的名称根据该字段的类型而有所不同。例如:
public class Response {
private Status status;
private String error;
private Object data;
[ getters, setters ]
}
我希望字段data
被序列化成类名而不是一个包含根据情况而异的不同类型的字段data
。
我该如何使用Jackson实现这样的技巧?
我使用@JsonAnyGetter
注解得到了一个更简单的解决方案,而且效果非常好。
import java.util.Collections;
import java.util.Map;
public class Response {
private Status status;
private String error;
@JsonIgnore
private Object data;
[getters, setters]
@JsonAnyGetter
public Map<String, Object> any() {
//add the custom name here
//use full HashMap if you need more than one property
return Collections.singletonMap(data.getClass().getName(), data);
}
}
不需要包装器,也不需要自定义序列化程序。
使用自定义JsonSerializer
。
public class Response {
private String status;
private String error;
@JsonProperty("p")
@JsonSerialize(using = CustomSerializer.class)
private Object data;
// ...
}
public class CustomSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeObjectField(value.getClass().getName(), value);
jgen.writeEndObject();
}
}
假设您想序列化以下两个对象:
public static void main(String... args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Response r1 = new Response("Error", "Some error", 20);
System.out.println(mapper.writeValueAsString(r1));
Response r2 = new Response("Error", "Some error", "some string");
System.out.println(mapper.writeValueAsString(r2));
}
第一个将会打印:{"status":"Error","error":"Some error","p":{"java.lang.Integer":20}}
还有第二个:
{"status":"Error","error":"Some error","p":{"java.lang.String":"some string"}}
我使用了名称为p
的包装对象,因为它仅仅充当一个占位符。如果你想要移除它,你需要为整个类编写自定义序列化器,即JsonSerializer<Response>
。
我的自己的解决方案。
@Data
@EqualsAndHashCode
@ToString
@JsonSerialize(using = ElementsListBean.CustomSerializer.class)
public class ElementsListBean<T> {
public ElementsListBean()
{
}
public ElementsListBean(final String fieldName, final List<T> elements)
{
this.fieldName = fieldName;
this.elements = elements;
}
private String fieldName;
private List<T> elements;
public int length()
{
return (this.elements != null) ? this.elements.size() : 0;
}
private static class CustomSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonProcessingException
{
if (value instanceof ElementsListBean) {
final ElementsListBean<?> o = (ElementsListBean<?>) value;
jgen.writeStartObject();
jgen.writeArrayFieldStart(o.getFieldName());
for (Object e : o.getElements()) {
jgen.writeObject(e);
}
jgen.writeEndArray();
jgen.writeNumberField("length", o.length());
jgen.writeEndObject();
}
}
}
}
JsonTypeInfo
,告诉Jackson确切的信息,您无需编写自定义序列化程序。有多种方法包含此信息,但对于您的具体问题,您将使用As.WRAPPER_OBJECT
和Id.CLASS
。例如:public static class Response {
private Status status;
private String error;
@JsonTypeInfo(include = As.WRAPPER_OBJECT, use = Id.CLASS)
private Object data;
}
然而,这种方法对于原始类型(如String或Integer)是无效的。对于原始类型,您也不需要该信息,因为它们在JSON中具有本机表示,并且Jackson知道如何处理它们。使用注释的附加好处是,如果您需要反序列化,则可以免费获得。以下是一个示例:
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Response r1 = new Response("Status", "An error", "some data");
Response r2 = new Response("Status", "An error", 10);
Response r3 = new Response("Status", "An error", new MyClass("data"));
System.out.println(mapper.writeValueAsString(r1));
System.out.println(mapper.writeValueAsString(r2));
System.out.println(mapper.writeValueAsString(r3));
}
@JsonAutoDetect(fieldVisibility=Visibility.ANY)
public static class MyClass{
private String data;
public MyClass(String data) {
this.data = data;
}
}
结果如下:
{"status":"Status","error":"An error","data":"some data"}
{"status":"Status","error":"An error","data":10}
{"status":"Status","error":"An error","data":{"some.package.MyClass":{"data":"data"}}}
根据@tlogbon的回复, 这是我包装具有特定/动态字段名称的项目列表的解决方案
public class ListResource<T> {
@JsonIgnore
private List<T> items;
@JsonIgnore
private String fieldName;
public ListResource(String fieldName, List<T> items) {
this.items = items;
this.fieldName = fieldName;
}
@JsonAnyGetter
public Map<String, List<T>> getMap() {
return Collections.singletonMap(fieldName, items);
}
@JsonAnySetter
,这里有一个很好的例子:https://www.tutorialspoint.com/jackson_annotations/jackson_annotations_jsonanysetter.htm - tlogbon