In addition to FastJson, you should also know about Jackson

Keywords: JSON Java codec

At the end of last month, I received a message about fastjson security vulnerability, and suddenly thought that there seemed to have been many such events (on fastjson). In terms of security, although the chance of getting shot is small, in this era of increasingly complex information, security has become more and more important. Just like the birth of devosecops, we need to pay attention to security in the whole value stream of software delivery. Of course, we won't talk about the advantages and disadvantages of fastjson now, because the goal of this article is to let you know and master Jackson.

overview

Jackson is a very popular and efficient Java based library that can serialize Java objects or map Java objects to JSON, and vice versa. Of course, in addition to Jackson, there are many excellent libraries of the same type in Java, such as:

There is no clear answer as to which is the best or which is the most popular. There are many kinds of technology, and everyone's attitude to different technology is different. Back to the point, the article is mainly about Jackson. This article focuses on two of the most common operations we deal with in Json:

  • Serializing Java objects to JSON
  • Deserializing JSON strings to Java objects

JavaObject to Json

ObjectMapper

ObjectMapper is a mapper (or data binder or codec) that provides the ability to transform between Java objects (instances of bean s) and JSON.

First, define a simple Java class

public class Car {
    private String color;
    private String type;
    // standard getters setters
}

Converting Java objects to Json

We use the writeValue Api of ObjectMapper to serialize Java objects

ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("blue","c1");
System.out.println(objectMapper.writeValueAsString(car));

Output at this time

{"color":"blue","type":"c1"}

more

The writeValue Api of ObjectMapper also provides many convenient Json serialization operation methods, such as: the writeValueAsBytes() method of serializing objects into Json byte array, the writeValue() method of custom output source

ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("blue","c1");
objectMapper.writeValue(new File("./xxx.txt"),car);

Run the above code, and the serialized Json of the Java object will be output to the xxx.txt Documents.

Json to JavaObject

Converting Json String to Java Object

ObjectMapper objectMapper = new ObjectMapper();
String json = "{\"color\":\"blue\",\"type\":\"c1\"}";
Car car = objectMapper.readValue(json, Car.class);

The readValue() method also accepts other forms of input, such as files containing JSON strings:

ObjectMapper objectMapper = new ObjectMapper();
Car car = objectMapper.readValue(new File("./xxx.txt"), Car.class);
System.out.println(car);

JSON to Jackson JsonNode

JsonNode

A JSON can be parsed into a JsonNode object to retrieve data from a specific node

Using the readTree() method, we can convert a Json string to a JsonNode

ObjectMapper objectMapper = new ObjectMapper();
String json = "{ \"color\" : \"Black\", \"type\" : \"FIAT\" }";
JsonNode jsonNode = objectMapper.readTree(json);
System.out.println(jsonNode.findValue("type").asText());
// Print out "FAIT"

JSONArrayString to JavaList

ObjectMapper objectMapper = new ObjectMapper();
String jsonCarArray =
    "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : 3. \"Red\", \"type\" : \"FIAT\" }]";
List<Car> listCar = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>() {});

JSONString to JavaMap

ObjectMapper objectMapper = new ObjectMapper();
String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
Map<String, Object> map = objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {
});

💡 : one of the biggest advantages of the Jackson library is the highly customizable serialization and deserialization process. Next, you'll look at some advanced features where the input or output JSON response can be different from the object that generated or used the response.

Configure serialization and deserialization features

String jsonString = "{ \"color\" : \"Black\", \"type\" : \"Fiat\", \"year\" :\"1970\" }";

Assuming that the above json string is used to deserialize into Java objects, the default parsing process will result in unrecognized propertyexception, because there is a new field year not included in the Car class.

Solve this problem by configuring the serialization and deserialization features:

ObjectMapper objectMapper = new ObjectMapper();
String jsonString = "{ \"color\" : \"Black\", \"type\" : \"Fiat\", \"year\" :\"1970\" }";
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Car car = objectMapper.readValue(jsonString, Car.class);

As above, we configured de in ObjectMapper serializationFeature.FAIL_ ON_ UNKNOWN_ Properties = false to ignore new fields.

**Similar to * * another option FAIL_ON_NULL_FOR_PRIMITIVES, which defines whether null values of the original value are allowed; FAIL_ON_NUMBERS_FOR_ENUM controls whether enum values are allowed to be serialized / deserialized to numbers

Custom serializer or deserializer

Custom serializer

public static class CustomCarSerializer extends StdSerializer<Car> {
    public CustomCarSerializer() {
        this(null);
    }

    public CustomCarSerializer(Class<Car> t) {
        super(t);
    }

    @Override
    public void serialize(Car car, JsonGenerator jsonGenerator, SerializerProvider serializer) throws IOException {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("car_brand", car.getType());
        jsonGenerator.writeEndObject();
    }
}

As mentioned above, we implemented a custom serializer by inheriting the StdSerializer class.

Use a custom serializer:

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("CustomCarSerializer", new Version(1, 0, 0, null, null, null));
module.addSerializer(Car.class, new CustomCarSerializer());
mapper.registerModule(module);
Car car = new Car("yellow", "enault");
System.out.println(mapper.writeValueAsString(car));
//Output {"car_brand":"enault"}

Custom deserializer

public static class CustomCarDeserializer extends StdDeserializer<Car> {

        public CustomCarDeserializer() {
            this(null);
        }

        protected CustomCarDeserializer(Class<?> vc) {
            super(vc);
        }

        @Override
        public Car deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            Car car = new Car();
            ObjectCodec codec = p.getCodec();
            JsonNode node = codec.readTree(p);
            // try catch block
            JsonNode colorNode = node.get("color");
            String color = colorNode.asText();
            car.setColor(color);
            return car;
        }
    }

As mentioned above, we implemented a custom serializer by inheriting the StdDeserializer class.

Use a custom deserializer:

String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\"}";
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("CustomCarDeserializer", new Version(1, 0, 0, null, null, null));
module.addDeserializer(Car.class, new CustomCarDeserializer());
mapper.registerModule(module);
Car car = mapper.readValue(json, Car.class);
//At this time, car {color='Black', type='null'}

Processing time format

⚠️ : only the processing of Java8's localdate & localdatetime is shown here

First, create a Car class with date and time fields

public class Car {
    private String color;
    private String type;
  	@JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDateTime produceTime;
    // standard getters setters
}

Custom time format processing

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
Car car = new Car().setColor("blue").setType("c1").setProduceTime(LocalDateTime.now());
String carAsString = objectMapper.writeValueAsString(car);
System.out.println(carAsString);
//At this time, the output: {"color": "blue", "type": "C1", "procetime": "2020-06-06"}

Working with collections

Another small but useful feature provided by the DeserializationFeature class is the ability to generate the collection types we want from JSON array responses.

String jsonCarArray = "[{ \"color\" : \"Black\", \"type\" : \"BMW\"}, { \"color\" : \"Red\", \"type\" : \"FIAT\"}]";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true);
Car[] cars = objectMapper.readValue(jsonCarArray, Car[].class);

As above, we converted a JsonArray string into an array of objects.

We can also convert it to a set:

String jsonCarArray = "[{ \"color\" : \"Black\", \"type\" : \"BMW\"}, { \"color\" : \"Red\", \"type\" : \"FIAT\"}]";
ObjectMapper objectMapper = new ObjectMapper();
List<Car> listCar = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>(){});

summary

Jackson is a reliable and mature JSON serialization / deserialization library for Java. The ObjectMapper API provides a simple way to parse and generate JSON response objects with great flexibility.

Pay attention to the author's official account and push all kinds of original / high quality technical articles. ⬇️

Posted by makoy on Fri, 05 Jun 2020 21:13:33 -0700