JSON 与 Protobuf

Posted by CoXier on September 30, 2017

今天是 9.30 日,国庆节的最后一天。这个工作周,最大的收获是接触了 protobuf ,同时也复习了 JSON。在我看来,一些东西应该是能乱记于心,这是(计算机)基础,发展之本。

一、JSON

JSON(JavaScript Object Notation)是一种轻量级的数据交换语言,具有一定的可读性,但是如果随着数据层级关系和内容数量增加,可读性会变得很糟糕。是作为数据交换语言,需要在 Server 和 Client 之间传递,所以在一定程度上,JSON 数据格式是独立于语言语法的。

JSON 的官方 MIME 类型是 application/json,文件扩展名是 .json。

1.1 JSON 数据格式

JSON 数据格式由两个基本数据结构组成。

  • 对象 Object :一个对象从 { 开始,以 } 结束。一个对象包含一系列非排序的键值对(key - value),键值对之间以 , 隔开
  • 键值对 key - value : key 和 value 之间以 : 隔开。

    “key” : value

    key 是一个字符串;value 可以是字符串、数值、布尔值、null、有序列表,甚至是一个 Object

一个有序列表从 [ 开始,以 ] 结束,包含了数据格式相同的数据。

举个例子:

{
  "school" : "Happy Child",
  "student" :
  [
    {
      "student_name" : "Bob",
      "student_age" : 1
    },
    {
      "student_name" : "Alice",
      "student_age" : 2
    }
  ],
  "location" : "US",
}

这个 json 实际上描述了: 美国 Happy Child 幼儿园有学生 Bob 和 Alice , Bob 的年龄是 5 岁,Alice 的年龄是 6 岁。

如果我现在想在此基础上增加一个信息:该校的学生数量是 2。就可以这样写:

{
  "school" : "Happy Child",
  "student" :
  [
    {
      "student_name" : "Bob",
      "student_age" : 1
    },
    {
      "student_name" : "Alice",
      "student_age" : 2
    }
  ],
  "location" : "US",
  "student_count" : 2
}

1.2 JSON 数据解析

前面说到 JSON 数据格式和语言相对独立,很多语言如 Java, Js, Python 都有 JSON 解析器,以 Java 为例来解析 JSON。在 Java 界说起 JSON 解析库,不得不提到 Google 的 Gson

模仿一个完整的 CS 请求,Node.js 写本地 http-server,Java 请求 server 获取 JSON 数据,然后解析 JSON 数据,转换成相应的 Java 对象。

server:

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

var jsonFile = require('./data.json')

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'application/json');
  res.write(JSON.stringify(jsonFile));
  res.end('\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

http-server 运行在本地 3000 端口。

client :

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
        .url("http://127.0.0.1:3000/")
        .build();
try {
    Response response = client.newCall(request).execute();
    Gson gson = new Gson();
    School school = gson.fromJson(response.body().string(), School.class);
} catch (IOException e) {
    e.printStackTrace();
}

二、 Protobuf

Protobuf(Protocol Buffer) 是 Google 开发的用来描述结构化数据的数据协议,在网络数据传输、数据存储有重要应用。Pb 和 JSON 类似也相对于语言、平台对立,以 .proto 文件描述 Pb 协议。上层应用要使用 Pb 协议时,使用相应的 pbc(Protocol Buffer Compiler)进行编译,将 .proto 文件转换成相应的 .cc.java 等文件。

由于历史原因, Pb 截至目前有两个版本 2.x 和 3.x , 3.x 支持的语言平台更多,而且语法更简单。

2.1 定义 proto 文件

还是 1.1 中的例子,定义两个文件 student.protoschool.proto。具体的语法规则,可以参考 Language Guide(proto3)

demo_student.proto :

syntax = "proto3";
option java_multiple_files = true;

message Student {
    string name = 1;
    int32 age = 2;
}

demo_school.proto :

syntax = "proto3";
option java_multiple_files = true;

import "demo_student.proto";

message School {
    string school = 1;
    string location = 2;
    int32 student_count = 3;
    repeated Student student = 4;
}

2.2 编译 proto 文件

编译 proto 文件需要用到 Protocol Buffer Compiler,下面先安装 protoc

安装编译工具

brew install autoconf automake libtool curl make unzip

如果不是 Mac ,根据系统的包管理工具安装。

生成 configure 脚本

./autogen.sh

最后安装即可:

$ ./configure
$ make
$ make check
$ sudo make install
$ sudo ldconfig # refresh shared library cache. (Mac 可以忽略此步骤)

安装完 protoc 后就可以编译了。编译命令:

protoc --proto_path=IMPORT_PATH 
--cpp_out=DST_DIR 
--java_out=DST_DIR 
--python_out=DST_DIR 
--go_out=DST_DIR 
--ruby_out=DST_DIR 
--javanano_out=DST_DIR 
--objc_out=DST_DIR 
--csharp_out=DST_DIR 
path/to/file.proto

虽然 protobuf 既提供了 java 也提供了 javanao,但 javanao生成的 java 文件很小,适用Android 平台。以 JavaNano 为例,生成 School.javaStudent.java

2.3 生成运行环境

有了 2.3 中的 class 是无法直接运行的,需要相应的运行环境,这里和 2.2 中保持一致均使用 javanao

mvn test
mvn install
mvn package

生成 protobuf-javanano-3.2.0.jar,这就是需要的 jar 文件。

2.4 测试

写个 序列化的例子来演示一下如何使用 javanano。

School school = new School();
school.location = "US";
school.school = "Happy Child";
school.studentCount = 2;
Student bob = new Student();
bob.age = 1;
bob.name = "Bob";

Student alice = new Student();
alice.age = 2;
alice.name = "Alice";

school.student = new Student[]{bob, alice};

byte[] result = MessageNano.toByteArray(school);

try {
    School newSchool = School.parseFrom(result);
} catch (InvalidProtocolBufferNanoException e) {
    e.printStackTrace();
}

本文对应的代码:https://github.com/CoXier/ComputerBasic