1,概念

1)Json(JavaScript Object Notation)

  1. json结构:大而占内存。
    不能有注释。只支持JSONObject、JSONArray两种结构。

JSONObject(json对象)
k-v对象,不能为null。

JSONArray(数组)
举例:[value1,value2,value3]

  1. 解析耗时;

1>JSON对比JSONP

JSONP(JSON with Padding).
json返回的是一串数据;
jsonp返回的是脚本代码(包含一个函数调用);

json是理想的数据交换格式,但没办法跨域直接获取,于是就将json包裹(padding)在一个合法的js语句中作为js文件传过去,来实现跨域获取数据。

2>json对比jsonb

json和jsonb都作为数据库的一种格式,被很多数据库使用:Postgresql 和 MongoDB 等。

主要区别:

区别jsonjsonb备注
存储方式保存为文本格式保存为二进制格式的
磁盘空间占用更多
读写更耗时
解析更快,操作更方便,很多函数支持。
  1. 如果你的应用只用json表示,PostgreSQL只用于保存与获取时,你应该使用json.
  2. 如果你需要在PostgreSQL中做比较多的json值的操作,或者在一些json字段上使用索引时,你应该使用jsonb

2)MessagePack

It’s like JSON.
but fast and small.

  1. 高效的二进制序列化格式,兼容跨语言;
  2. 比json快、压缩率高;
  3. 兼容性差,必须按照顺序保存字段。

3)xml

  1. 比json描述性更高。
  2. 编码解码性能差。

4)ProtocolBuffer(protobuf)

  1. google出品,一般用于传输数据比较大的API。
  2. 强大,灵活,但是对内存的消耗会比较大。
  3. Netty的高性能就默认提供了对GoogleProtobuf的支持。
  4. 是否支持跨语言:c++、java、Python.

5)FlatBuffer

是google的一个跨平台串行化库,开发这个最初是用在游戏项目中,是二进制存储和二进制读取,不需要解析,通过fb的二进制文件生成对象的时间可以忽略。需要通过工具把普通model文件生成flatBuffer格式的model文件。flatBuffer解析library就四个文件,代码很少。本地cache比较大的可以考虑通过这种方式存储和读取。
Json是当今全服务平台的轻量级数据传输格式,Json量级轻,并且可读性强,使用友好却不过时,Json是语言独立的数据格式,但Json转换的时候却耗费较多的时间和内存,Facebook尝试过将所有的App中Json传输都换成使用Flatbuffers,而最终的效果遵循Android快速响应界面的16ms原则。

6)Nano-Proto-Buffers

基于Protocal,为移动终端做了特殊的优化,代码执行效率更高,内存使用效率更佳。

7)protostuff

8)thrift

9)hession

10)kryo

11)avro

12)fst

2,json框架使用

1)json-lib库(net.sf.json-lib)

json-lib是一个纯Java的开源库。

  1. 使用率低、性能低。
  2. 不支持类包含Map属性或者Map属性中包含List属性;

1>String转换为JSONObject

JSONObject jsonObject = JSONObject.fromObject(jsonStr);

2>JSONObject转换为java对象

Person person = (Person)JSONObject.toBean(obj,Person.class);

3>java对象转换为String

//将java对象转换为json对象
//将json对象转换为字符串
String str = json.toString();

4>json数组转java数组

//将JSON数组转换为Java数组
String[] strArray = (String[]) JSONArray.toArray(jsonArray, String.class);

2)FastJson框架(com.alibaba.fastjson)

特点:

  1. 速度快;
  2. 严格按照JavaBean的规范;
    实体类有一点不对就无法解析。而Gson就无此要求;

JavaBean规范
在fastJson中,解析用到的实体类必须严格按照JavaBean规范来写。
特点:

  1. public类
  2. 有一个无参数的构造方法。
  3. private属性、有getXxx()和setXxx方法(方法的第四个字母为大写)。
  4. 必须实现序列号接口。
  1. fastjson会把数值类型的null(如Integer)转换为0;

1>序列化&反序列化

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;

//Java Bean序列化
String str = JSONObject.toJSONString(obj)//Java Bean反序列化1
People people = (People)JSONObject.parseObject(str, People.class);
//Java Bean反序列化2   支持泛型
Result<People> obj = (Result<People>) JSON.parseObject(js, new TypeReference<Result<People>>(){});

//集合 序列化
String str = JSONArray.toJSONString(list); 
//集合 反序列化1
List<People> list = JSONArray.parseArray(str, People.class);
//集合 反序列化2  支持泛型
List<People> list = JSON.parseObject(json, new TypeReference<List<People>>(){});

//Map 序列化 同上
//Map 反序列化
Map map= (Map)JSONObject.parseObject(str);

2>重复数据$ref

循环引用:当一个对象包含另一个对象时,fastjson就会把该对象解析成引用。引用是通过$ref标示的,下面介绍一些引用的描述

"$ref":".." 上一级
"$ref":"@" 当前对象,也就是自引用
"$ref":"$" 根对象
"$ref":"$.children.0" 基于路径的引用,相当于 root.getChildren().get(0)

3>日期处理


        User user = new User();
        user.setDate(new Date);
        // 序列化   {"date":1516884789616}
        System.out.println(JSON.toJSONString(user));

        // 序列化处理时间,方式一   {"date":"2018-01-25 20:53:09.616"}
        System.out.println(JSON.toJSONStringWithDateFormat(user, "yyyy-MM-dd HH:mm:ss.SSS"));

        // 序列化处理时间,方式二:ISO-8601日期格式   {"date":"2018-01-25T20:53:09.616+08:00"}
        System.out.println(JSON.toJSONString(user, SerializerFeature.UseISO8601DateFormat));

        // 序列化处理时间,方式三:全局修改日期格式    {"date":"2018-01-25"}
        JSON.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd";
        System.out.println(JSON.toJSONString(user, SerializerFeature.WriteDateUseDateFormat));

4>枚举处理


5>自省处理

6>反序列化漏洞

v1.2.69中已修复;v1.2.68可通过safeMode规避。
fastjson序列化一个子类时,会抹去子类只保留父类或接口,此时反序列化无法拿到原始类型。为解决这个问题,fastjson引入@type,试图把json内容反序列化成这个对象,并调用setter方法。==》攻击者使用@type,指定一个想要攻击的类库json串,通过set方法可以调用攻击命令。

3)Gson

  1. Google API,适用于小量的数据解析
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.2</version>
</dependency>

1>基本数据类型的解析:

Gson gson = new Gson();
int i = gson.fromJson("100", int.class);              //100

2>基本数据类型的生成:

Gson gson = new Gson();
String jsonNumber = gson.toJson(100);      // 100

3>封装通用类

package util;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import entity.DraftStorgeProblemEntity;
import entity.UploadEntity;

import java.lang.reflect.Type;
import java.util.List;

/**
 * Java对象和JSON字符串相互转化工具类
 * @author luo
 * @date 2018-06-21
 */
public final class JsonUtil {

    private JsonUtil(){}

    /**
     * 对象转换成json字符串
     * @param obj
     * @return
     */
    public static String toJson(Object obj) {
        Gson gson = new Gson();
        return gson.toJson(obj);
    }

    /**
     * json字符串转成对象
     * @param str
     * @param type
     * @return
     *
     * 调用方法:
    UploadEntity<ProblemEntity> uploadEntity1 = JsonUtil.fromJson(jsonStr, new TypeToken<UploadEntity<ProblemEntity>>() {
    }.getType());
     */
    public static <T> T fromJson(String str, Type type) {
        Gson gson = new Gson();
        return gson.fromJson(str, type);
    }

    /**
     * json字符串转成对象
     * @param str
     * @param type
     * @return
     *
     * 调用方法:
    UploadEntity<ProblemEntity> uploadEntity1 = JsonUtil.fromJson(jsonStr, UploadEntity.class);
     但这样无法解析内层的ProblemEntity。建议采用TypeToken来调用
     */
    public static <T> T fromJson(String str, Class<T> type) {
        Gson gson = new Gson();
        return gson.fromJson(str, type);
    }

    /**
     * 列表转换为JsonArray
     * @param list
     * @param <T>
     * @return
     */
    public static <T> JsonArray list2JsonArray(List<T> list){

        return new Gson().toJsonTree(list, new TypeToken<List<T>>() {
        }.getType()).getAsJsonArray();
    }

    /**
     * jsonArray转List
     * @param jsonArray
     * @param clazz
     * @param <T>
     * @return
     *
     * 使用:List<UploadEntity> list1 = (List<UploadEntity>) JsonUtil.jsonArray2List(jsonArray, UploadEntity.class);
     */
    public static <T> List<T> jsonArray2List(JsonArray jsonArray, Class<T> clazz) {

                return new Gson().fromJson(jsonArray,new TypeToken<List<T>>() {
                }.getType());
    }
    /**
     * Object转JsonObject
     * @param obj
     * @return
     */
    public static JsonObject object2JsonObject(Object obj){

        return new Gson().toJsonTree(obj).getAsJsonObject();
    }
    /**
     * JsonString转JsonObject
     * @param str
     * @return
     */
    public static JsonObject jsonString2JsonObject(String str){

        return new JsonParser().parse(str).getAsJsonObject();
    }
}

4)Jackson框架

  1. 实现json、xml与java对象的互相转换;
  2. 适用于大量的数据解析。
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.4</version>
        </dependency>
        <!--引入jackson-databind会关联引入jackson-annotations和jackson-core-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.4</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.4</version>
        </dependency>
        
        <!--xml转换-->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>

1>常用注解

json注解场景说明备注
@JsonProperty把该属性的名称序列化为另外一个名称。
@JsonIgnore序列化的时忽略,不序列此字段。不适用于fastjson
@JSONField(serialize = false)fastjson 序列化时字段及是否序列化
@JsonIgnoreProperties类注解,作用是json序列化时将java bean中的一些属性忽略掉,序列化和反序列化都受影响。
@JsonIgnoreType标注在类上,当其他类有该类作为属性时,该属性将被忽略。
@JsonComponent为指定类设置序列化规则,无需其他任何额外配置,即可生效。
@JsonFormat为指定字段设置序列化规则,优先级高于JsonComponent。
@JsonInclude如果该属性为NULL则不参与序列化如果放在类上边,那对这个类的全部属性起作用。
@JsonSerialize此注解用于属性或者getter方法上,用于在序列化时嵌入我们自定义的代码,比如序列化一个double时在其后面限制两位小数点。
@JsonDeserialize此注解用于属性或者setter方法上,用于在反序列化时可以嵌入我们自定义的代码,类似于上面的@JsonSerialize
@Transient如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则ORM框架默认其注解为@Basic;
@JsonPropertyOrder可以指定属性在 json 字符串中的顺序。
@JsonSetter标注于 setter 方法上,类似 @JsonProperty ,也可以解决 json 键名称和 java pojo 字段名称不匹配的问题。
xml注解作用域说明实现时机
@JacksonXmlRootElement类上指定 XML 根标签的名字序列化时
@JacksonXmlProperty属性或getter/setter方法上指定属性对应 XML 映射的名称序列化和反序列化时
@JacksonXmlText属性或getter/setter方法上将属性直接作为未被标签包裹的普通文本序列化和反序列化时
@JacksonXmlCData属性或getter/setter方法上将属性值包裹在 CDATA 标签中序列化时
@JacksonXmlElementWrapper属性或getter/setter方法上类中有集合的属性时,是否生成包裹的标签序列化时
  1. @JsonProperty
public class Apple {
		//序列化后键值为fake_name而不是fakeName
    @JsonProperty(value = "fake_name", required = true)
    private String fakeName;
}
  1. @JsonIgnoreProperties
//生成json时将name和age属性过滤
@JsonIgnoreProperties({"name"},{"age"})
public class  user {
 
    private  String name;
    private int age;
}
  1. @JsonFormat
    日期格式化输出。
public class User{
    @JsonFormat(pattern="yyyy-MM-dd",timezone = "GMT+8")
    private Date date;
}

关于日期的格式化,也可以在yaml中配置:

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    # 如果打开 write-dates-as-timestamps,会自动将日期转换为时间戳。
    serialization:
      write-dates-as-timestamps: false
  1. @JsonInclude
@JsonInclude(Include.NON_NULL) 

//Include.Include.ALWAYS 默认
//Include.NON_DEFAULT 属性为默认值不序列化
//Include.NON_EMPTY 属性为 空(“”) 或者为 NULL 都不序列化
//Include.NON_NULL 属性为NULL 不序列化 

2>转换

Apple apple = new Apple();
        apple.setFakeName("a");
        apple.setFakeOld(1);

        List<Apple> apples = new ArrayList<>();
        apples.add(apple);

        ObjectMapper mapper = new ObjectMapper();

        //Object转json字符串
        String str = mapper.writeValueAsString(apple);
        String listStr = mapper.writeValueAsString(apples);
        System.out.println("str is " + str + ", apples ListStr is " + listStr);

        //json字符串转换为对象
        Apple app = mapper.readValue(str, Apple.class);
        System.out.println(app);

        //json字符串转换为list对象
        JavaType javaType = mapper.getTypeFactory().constructParametricType(List.class, Apple.class);
        List<Apple> appleList = mapper.readValue(listStr, javaType);
        System.out.println(appleList);

3,MessagePack序列化框架

<dependency>
            <groupId>org.msgpack</groupId>
            <artifactId>msgpack-core</artifactId>
            <version>0.8.16</version>
        </dependency>
        <dependency>
            <groupId>org.msgpack</groupId>
            <artifactId>jackson-dataformat-msgpack</artifactId>
            <version>0.9.0</version>
        </dependency>
        <dependency>
            <groupId>org.msgpack</groupId>
            <artifactId>msgpack</artifactId>
            <version>0.6.12</version>
        </dependency>

序列化与反序列化:

//注:使用MessagePack序列化时,该对象必须在类上加@Message注解
User user = new User();
user.setId(1);
user.setName("张三");

MessagePack msgpk = new MessagePack();
byte[]  msgBtyes = msgpk.write(user);
System.out.println("MessagePack序列化后流的大小:"+msgBtyes.length);
//输出结果:MessagePack序列化后流的大小:9


//反序列化对象
User userNew =msgpk.read(msgBtyes,User.class);

4,protobuf序列化框架

1)依赖和环境准备

<dependency>
    <groupId>com. Google .protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.9.0-rc-1</version>
</dependency>
  1. protobuf是用C ++编写的。对于非C ++用户,需要从github上下载预构建的二进制文件: ,

找到对应自己系统版本, 找到C++最新版本的 :protoc-3.9.0-rc-1-win64.zip,下载好后解压,后续会通过cmd命令生产java文件。
注: maven中protobuf的version要与你下载的生成器版本保持一致,不然会出现很多问题。

2)创建文件

  1. 手动新建User.proto文件
syntax = "proto2";//固定

package protobuf; //当前文件路径

option java_package = "com.protobuf";//生成Java类后存放路径,生成java文件时会自动创建
option java_outer_classname = "UserProto";//类名,UserProto.java

//内部类,用来保存实例对象
message UserProtoBuf {
  required int64 id = 1; //   类型Long   指定唯一序号, 这个主要用于列对应的值
  required string name = 2; 
}  
  1. cmd命令进入到我们下载的工具bin路径下
protoc -I=D:toolssrcprotobuf --java_out=D:toolssrcmainjavacomprotobuf User.proto  

 -I=D:toolssrcprotobuf 表示你User.proto的物理路径
 –java_out=D:toolssrcmainjava 表示生成java类保存的路径
 User.proto 表示你对哪一个proto文件生成java类 
  1. 检查指定路径下是否生成的UserProto.java

UserProto.java类中会有部分方法java.lang.Override报错,这是因为JDK版本原因,删掉即可。
如果出现部分方法错误,请检查自己的maven版本号和生成器的版本号是否一致。

3)java代码

//对内部类赋值
UserProto.UserProtoBuf userProBuf = UserProto.UserProtoBuf.newBuilder().setId(1L).setName("张三").build();
byte[]  bytes  = user.toByteArray();
//输出结果protobuf流长度:10
System.out.println("protobuf流长度:" + bytes.length);

//反序列化调用
UserProto.UserProtoBuf user = UserProto.UserProtoBuf.parseFrom(byte[] data);  

5,java序列化

序列化:
将对象转换未可传输格式的过程,是一种数据持久化手段。

1)java如何支持序列化?

支持序列化的类型:

  1. String、enum、Array、基本数据类型的封装类型;
  2. 实现了Serializable接口的类。
    缺点:字节形式,可读性差;占用内存大。

2)修改字节码后是否还可以反序列化成功?

——会报错。

jvm是否支持反序列化,取决于以下2点:

  1. 类路径和功能代码一致;
  2. 两个类的序列化ID(serialVersionUID)一致。
    避免文件被篡改。
    如果没有定义serialVersionUID,那么java序列化机制会自动生成一个serialVersionUID变量,无论编译多次都不会变化,除非发生变更。
    自动生成的serialVersionUID反序列化时偶尔会报错,建议显式定义,用private修饰。
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐