在上月末的時候收到一條關於fastjson安全漏洞的訊息,突然想到先前好像已經有好多次這樣的事件了(在fastjson上面)。關於安全方面,雖然中槍的機率微小,但是在這個資訊越來越複雜的時代,安全性也變得越來越重要,就像DevSecOps的誕生,在軟體交付的整個價值流中我們也需要注重安全這方面。當然我們現在不談關於FastJson的優劣,因為我們本文的目標是讓大家瞭解和掌握Jackson。
概覽
Jackson是一個非常流行和高效的基於Java的庫,它可以序列化java物件或將java物件對映到JSON,反之亦然。當然除了Jackson,在Java中同型別的優秀的庫也有很多,比如:
關於哪一個最好或者哪一個最流行,沒有明確的答案。技術的種類繁多,每個人對與不同技術的態度也是不一樣。言歸正傳,文章主要還是討論Jackson的。本文主要講解我們處理Json中最常見的兩個操作:
- 將Java物件序列化為JSON
- JSON字串反序列化為Java物件
JavaObject to Json
ObjectMapper
ObjectMapper是一個對映器(或資料繫結器或編解碼器),提供了在Java物件(bean的例項)和JSON之間進行轉換的功能。
首先定義一個簡單的Java類
public class Car {
private String color;
private String type;
// standard getters setters
}
將Java物件轉換成Json
我們使用ObjectMapper的writeValue相關Api來對Java物件進行序列化操作
ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("blue","c1");
System.out.println(objectMapper.writeValueAsString(car));
此時輸出
{"color":"blue","type":"c1"}
更多
ObjectMapper的writeValue相關Api還提供了很多便利的Json序列化操作方法,比如:將物件序列化成Json位元組陣列的writeValueAsBytes()
方法、自定義輸出源的writeValue()
方法...
ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("blue","c1");
objectMapper.writeValue(new File("./xxx.txt"),car);
執行上述程式碼,Java物件的序列化Json將被輸出到xxx.txt檔案。
Json to JavaObject
將Json String轉換成Java Object
ObjectMapper objectMapper = new ObjectMapper();
String json = "{\"color\":\"blue\",\"type\":\"c1\"}";
Car car = objectMapper.readValue(json, Car.class);
readValue()方法也接受其他形式的輸入,比如包含JSON字串的檔案:
ObjectMapper objectMapper = new ObjectMapper();
Car car = objectMapper.readValue(new File("./xxx.txt"), Car.class);
System.out.println(car);
JSON to Jackson JsonNode
JsonNode
一個JSON可以被解析成一個JsonNode物件,用來從一個特定的節點檢索資料.
使用readTree()方法,我們可以將Json字串轉換成JsonNode
ObjectMapper objectMapper = new ObjectMapper();
String json = "{ \"color\" : \"Black\", \"type\" : \"FIAT\" }";
JsonNode jsonNode = objectMapper.readTree(json);
System.out.println(jsonNode.findValue("type").asText());
// 列印出“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>>() {
});
?:Jackson庫最大的優點之一是高度可定製的序列化和反序列化過程。接下來將介紹一些高階特性,其中輸入或輸出JSON響應可以與生成或使用響應的物件不同。
配置序列化和反序列化特性
String jsonString = "{ \"color\" : \"Black\", \"type\" : \"Fiat\", \"year\" :\"1970\" }";
假設使用如上json字串來反序列化成Java物件,按照預設解析過程將導致UnrecognizedPropertyException exception異常。
通過配置序列化和反序列化特性來解決此問題:
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);
如上,我們在ObjectMapper中配置了DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES=false
,從而實現忽略新的欄位。
類似:另一個選項FAIL_ON_NULL_FOR_PRIMITIVES
,它定義了是否允許原始值的空值;FAIL_ON_NUMBERS_FOR_ENUM
控制是否允許enum值被序列化/反序列化為數字...
自定義序列化器或反序列化器
自定義序列化器
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();
}
}
如上,通過整合StdSerializer類,我們實現了一個自定義的序列化器。
使用自定義的序列化器:
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));
//輸出{"car_brand":"enault"}
自定義反序列化器
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;
}
}
如上,通過整合StdDeserializer類,我們實現了一個自定義的序列化器。
使用自定義的反序列化器:
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);
//此時的car {color='Black', type='null'}
處理時間格式
⚠️:此處僅展示對於Java8的LocalDate&LocalDateTime的處理
首先建立一個帶日期時間欄位的Car類
public class Car {
private String color;
private String type;
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDateTime produceTime;
// standard getters setters
}
自定義時間格式處理
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);
//此時輸出:{"color":"blue","type":"c1","produceTime":"2020-06-06"}
處理集合
DeserializationFeature類提供的另一個小但有用的特性是能夠從JSON陣列響應生成我們想要的集合型別。
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);
如上,我們將一個JsonArray字串轉換成了物件陣列。
我們也可以將其轉換成集合:
String jsonCarArray =
“[{ \”color\” : \”Black\”, \”type\” : \”BMW\” }, { \”color\” :
\”Red\”, \”type\” : \”FIAT\” }]”;
ObjectMapper objectMapper = new ObjectMapper();
List<Car> listCar = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>(){});
// print cars
總結
Jackson是一個可靠而成熟的用於Java的JSON序列化/反序列化庫。ObjectMapper API提供了一種簡單的方法來解析和生成JSON響應物件,具有很大的靈活性。
關注筆者公眾號,推送各類原創/優質技術文章 ⬇️