对于如下类型定义TestTaskInfo
,props
字段为JSON字符串(这在数据库经常用到),可以自由保存各种类型的数据
@Data
public class TestTaskInfo {private String id;private String props;public TestTaskInfo() {}public TestTaskInfo(String id, String props) {super();this.id = id;this.props = props;}
}
如果对这样的字段直接序列化,效果就是这样:
TestTaskInfo info= new TestTaskInfo("0000", "{\"task_name\":\"RESET\",\"target_id\":22}");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(info);
System.out.println(json);
输出
{"id":"0000","props":"{\"task_name\":\"RESET\",\"target_id\":22}"}
这个结果props
字段被序列化为字符串,如果送给前端并不友好,前端还需要再对props
字段进行解析才能得到里面的值。
@JsonRawValue
jackson的注解com.fasterxml.jackson.annotation.JsonRawValue
可以解决上面的问题,它指示应按原样包含属性的文字字符串值来序列化字段。
所以给props
字段增加@JsonRawValue
就可以在序列化时将它不加双引号号,直接输出一个JSON对象而非字符串
@Data
public class TestTaskInfo {private String id;@JsonRawValueprivate String props;public TestTaskInfo() {}public TestTaskInfo(String id, String props) {super();this.id = id;this.props = props;}
}
效果是这样:
TestTaskInfo info= new TestTaskInfo("0000", "{\"task_name\":\"RESET\",\"target_id\":22}");
ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
String json = mapper.writeValueAsString(info);
System.out.println(json);
输出:
{"id" : "0000","props" : {"task_name":"RESET","target_id":22}
}
RawJsonDeserializer
解决了JSON字符串(String类型)字段序列化的问题,那么反序列化的问题又来了。
{"id" : "0000","props" : {"task_name":"RESET","target_id":22}
}
上面的输出在反序列化为TestTaskInfo
对象时就会出问题,因为props
的类型是String
,但这个JSON中props是个OBJECT
,所以在反序列化时会报错
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
要解决反序列化的问题,就得实现自定义的反序列化器。不论输入的JSON类型是什么,直接将内容段反序列化为String。这样就与props字段的类型匹配了。
以下是自定义反序列化器的实现:
import java.io.IOException;import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/*** Deserializing JSON property as String with Jackson<br>* 实现将有{@link com.fasterxml.jackson.annotation.JsonRawValue}注解的* 内容为JSON的String类型字段反序列化为String的反序列化器实现**/
public class RawJsonDeserializer extends JsonDeserializer<String> {@Overridepublic String deserialize(JsonParser jp, DeserializationContext ctxt)throws IOException, JsonProcessingException {ObjectMapper mapper = (ObjectMapper) jp.getCodec();JsonNode node = mapper.readTree(jp);/** 将节点树输出为String */return mapper.writeValueAsString(node);}
}
我们再修改TestTaskInfo
类型定义为props
字段增加注解定义反序列化器:
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import gu.sql2java.json.RawJsonDeserializer;import lombok.Data;@Data
public class TestTaskInfo {private String id;@JsonRawValue@JsonDeserialize(using = RawJsonDeserializer.class)private String props;public TestTaskInfo() {}public TestTaskInfo(String id, String props) {super();this.id = id;this.props = props;}
}
再次测试:
import static org.junit.Assert.*;import org.junit.Test;import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;public class RawJsonDeserializerTest {@Testpublic void testJacksonCodec() {try {TestTaskInfo info= new TestTaskInfo("0000", "{\"task_name\":\"RESET\",\"target_id\":22}");ObjectMapper mapper = new ObjectMapper()/* .enable(SerializationFeature.INDENT_OUTPUT) */;/** 序列化 */String json = mapper.writeValueAsString(info);System.out.println(json);/** 反序列化 */TestTaskInfo parsed = mapper.readValue(json, TestTaskInfo.class);System.out.println(parsed.toString());assertTrue(parsed.equals(info));} catch (Throwable e) {e.printStackTrace();fail();}}}
这样就可以正常反序列化了,
输出
{“id”:“0000”,“props”:{“task_name”:“RESET”,“target_id”:22}}
TestTaskInfo(id=0000, props={“task_name”:“RESET”,“target_id”:22})
参考资料
《Deserializing JSON property as String with Jackson》