environment
java:1.8+
jackson-databind:2.8.11
preface
Recently, when making requirements, the parsed json is strange. There are multiple layers of json nested inside, and there will be single quotation marks in these nested json. After using Java to store into the database, there will be escape characters. In this way, an error will be reported during deserialization and the parsing fails.
At first, I used fastjason to deserialize, but the support for single quotation marks and escape characters is really not good enough.
Later, jackson is used instead. It is convenient for escape characters, single quotation marks, null, empty strings and so on. It has a very good solution.
Introduce dependency
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.11</version> </dependency>
usage method
Create objectMapper
In the first step, we need to create an ObjectMapper object through which we can serialize or deserialize.
ObjectMapper objectMapper = new ObjectMapper();
Simple use of serialization and deserialization
For simple parsing:
User user = new User(); user.setName("rongK"); user.setAge(28); user.setCreateTime(new Timestamp(new Date().getTime())); //serialize String s = objectMapper.writeValueAsString(user); System.out.println("Object serialization:" + s);// Object serialization: {name":"rongK","age":28,"createTime":1598081001761} //Deserialization User u = objectMapper.readValue(s, User.class); System.out.println("Object deserialization:" + u);// Object deserialization: user (name = longk, age = 28, createtime = 2020-08-22 15:23:21.761)
The above example is simple to use, but there are some situations as follows:
① There is a field in the deserialized json, which is not in the POJO class
② There are some problems with the deserialized json format:
{ //User is a POJO, not a string //By default, deserialization fails "User": "" }
③ There are single quotes in deserialization json:
{"expression":"${detail.putDetailJsonParam(execution,ruleService.mergeFilterKey(null,null,jsonUtils.parseObject('{"orderId":'.concat(orderId).concat('}')),'preposition','sgy',jsonUtils.parseJson2Array('["info","attributeJSON"]','java.lang.String')).infoJson)}"
It can be seen that value is a string, but there are nested use of single quotation marks and double quotation marks in the string.
④ There are escape characters or special characters;
For use problems, Jackjson can help us solve them through some configurations;
Ignore unmapped fields
When we deserialize, there are often fields in the class that do not exist in json, such as:
There is a Properties class in which only two fields are defined:
@Data public class Properties { private Document document; private String name; }
However, there are fields in our json string that are not defined by the Properties class:
{ "properties": { "defaultflow":false, "executionlisteners":"", "documentation":"", "overrideid":"", "name":"test" } }
At this time, we need to ignore the mapped fields
Method 1:
@JsonIgnoreProperties(ignoreUnknown = true)
In the POJO class,
Disadvantages: POJO classes combined in POJO also need to be added
Method 2:
Recommended use
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Single quotation mark solution
//Single quotation marks are allowed objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) ;
Solutions to null and empty strings
//null and empty strings are supported objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
It should be noted here that the following scenarios can not solve the problem even if the above settings are made
{ //User is a POJO, not a string //By default, deserialization fails "User": "" }
Methods to solve escape characters and special fields
//Special characters and escape characters are allowed objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true) ;
Resolve deserialization of nested string objects with Jackson (quoted)
The assumptions are as follows
{ "id": "abcd1234", "name": "test", "connections": { "default": "http://foo.com/api/", "dev": "http://dev.foo.com/api/v2" }, "document": "",//document is essentially a class with two fields "settings": { "foo": "{\n \"fooId\": 1, \"token\": \"abc\"}", "bar": "{\"barId\": 2, \"accountId\": \"d7cj3\"}" } }
@Data public class Documentation { private String name; private String value; }
In this case, we need to deal with the construction process of deserialized related classes
@Data public class Documentation { private String name; private String value; /** * The required for deserialization supports the scenario of Documentation: ''. * @param str * @return * @throws IOException */ @JsonCreator public static Documentation create(String str) throws IOException { //This place is for processing '' if (StringUtils.isBlank(str)) { return null; } //In fact, value is extracted separately and deserialized again; return (new ObjectMapper()).readValue(str, Documentation.class); } }
explain:
- During deserialization, Jackson will call the object's parameterless constructor by default. If we do not define any constructor, the JVM will be responsible for generating the default parameterless constructor. However, if we define a constructor and do not provide a parameterless constructor, Jackson will report an error;
- @JsonCreator this annotation is used to specify a specific constructor or factory method when deserializing an object. If the default constructor cannot meet the requirements, or we need to do some special logic when constructing objects, we can use this annotation.
- If it is a constructor, it needs to be used in conjunction with @ JsonProperty:
public class Person { private int age; private String name; @JsonCreator public Person(@JsonProperty("age") int age, @JsonProperty("name") String name) { this.age = age; this.name = name; } }
- If it is a static method, it is not required; The example is the Documentation example I used above.
summary
Jack JSON is really much better than fast JSON in these special processing.
Assuming that the default deserialization constructor (no parameters) cannot meet our requirements, we can specify the constructor or static method through @ JsonCreator. The constructor needs to be used with @ JsonProperty, while the static method does not.
Attachment configuration details
//This feature determines whether the parser will automatically close the input sources that do not belong to the parser itself. // If prohibited, the calling application has to close the basic input streams InputStream and reader used to create the parser respectively; //The default is true objectMapper.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, true); //Whether to allow parsing of comments using Java/C + + style (including '/' + '*' and '/ /' variables) objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true); //When set to true, the property name is not enclosed in double quotes objectMapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false); //Deserialization is whether attribute names are allowed without double quotes objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); //Whether single quotation marks are allowed to enclose attribute names and string values objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); //Whether JSON strings are allowed to contain non quote control characters (ASCII characters with values less than 32, including tabs and line breaks) objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true); //Allow JSON integers to start with multiple zeros objectMapper.configure(JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS, true); //null properties are not serialized objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); //Sort attributes alphabetically. The default is false objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY,true); //Whether to take the class name as the root element, you can customize the root element name through @ JsonRootName. The default is false objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE,true); //Whether to scale and arrange the output. The default is false objectMapper.configure(SerializationFeature.INDENT_OUTPUT,false); //When serializing Date, it is output as timestamps. The default is true objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,true); //Whether the serialized enumeration is output as toString(), which is false by default, that is, it is output as name() by default objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true); //Whether the serialized enumeration is output as ordinal(), which is false by default objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_INDEX,false); //When serializing a single element array, it is not output as an array. The default is false objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING,true); //When serializing a Map, sort the key s. The default is false objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS,true); //When serializing char [], it is output as json array. The default is false objectMapper.configure(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS,true); //Whether to output the original number or scientific count when serializing BigDecimal. The default is false, that is, it is output in the form of toPlainString() scientific count objectMapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN,true);
@JsonFormat(pattern = "yyyy-MM-dd'T' HH:mm:ss:SSS'Z'",timezone = "GMT+8") Time format annotation type must be Date,Otherwise, it will not take effect
Direct reference address
Deserializing stringified (quote enclosed) nested objects with Jackson