跳转到内容

手写JSON MiniFastJson

FastJSON 本身是 Java JSON 库,核心能力是 Java 对象和 JSON 字符串之间的序列化、反序列化;官方仓库也提示 FastJSON 2.x 已发布并推荐升级。

一、项目目标

项目定位

JavaSE 手写一个简化版 JSON 解析工具,实现:

JsonObject obj = ZXJson.parseObject(json);
String name = obj.getString("name");

User user = ZXJson.parseObject(json, User.class);

String jsonStr = ZXJson.toJSONString(user);
  1. JSON 字符串是如何被拆成 Token 的;
  2. Token 是如何被解析成对象树的;
  3. 嵌套对象、数组、字符串、数字、布尔值、null 是如何表示的;
  4. JavaBean 如何和 JSON 互相转换;
  5. FastJSON、Jackson、Gson 这类工具底层大概怎么工作。

二、复杂 JSON 示例

{
  "code": 200,
  "message": "success",
  "data": {
    "user": {
      "id": 1001,
      "name": "周鑫",
      "active": true,
      "roles": ["admin", "teacher", "student"],
      "profile": {
        "age": 22,
        "email": "test@example.com",
        "score": 98.5
      }
    },
    "courses": [
      {
        "id": 1,
        "name": "JavaSE",
        "tags": ["基础", "面向对象", "集合"]
      },
      {
        "id": 2,
        "name": "Spring Boot",
        "tags": ["后端", "项目实战"]
      }
    ]
  },
  "error": null
}

这个 JSON 覆盖了:


三、整体技术路线

手写 JSON 解析器建议分成两个核心阶段:

JSON 字符串
   ↓
词法分析 Lexer / Tokenizer
   ↓
Token 列表
   ↓
语法分析 Parser
   ↓
JsonObject / JsonArray / JsonValue
   ↓
JavaBean 映射 / 序列化输出

你给的第一篇参考文章也采用这个思路:JSON 解析过程通常包括词法分析和语法分析,词法分析把字符串拆成 Token,语法分析再检查 Token 是否符合 JSON 文法。


四、项目阶段规划

阶段一:认识 JSON 结构

目标

让学生先不写代码,先理解 JSON 的基本语法。

要掌握的内容

JSON 支持的基本元素:

对象:{}
数组:[]
字符串:"hello"
数字:123 / 12.5 / -10
布尔值:true / false
空值:null
分隔符:: ,

JSON 对象本质上是:

key-value 键值对

JSON 数组本质上是:

多个 value 的有序集合

阶段产出

写出项目语法规则文档:

object = { } | { members }
members = pair | pair , members
pair = string : value
array = [ ] | [ elements ]
elements = value | value , elements
value = string | number | object | array | true | false | null

阶段二:实现 Token 类型

目标

先定义 JSON 中会出现的所有 Token。

TokenType 枚举设计

public enum TokenType {
    LEFT_BRACE,      // {
    RIGHT_BRACE,     // }
    LEFT_BRACKET,    // [
    RIGHT_BRACKET,   // ]
    COLON,           // :
    COMMA,           // ,
    STRING,          // "hello"
    NUMBER,          // 123 / 12.5
    TRUE,            // true
    FALSE,           // false
    NULL,            // null
    EOF              // 结束
}

Token 类设计

public class Token {
    private TokenType type;
    private String value;
    private int line;
    private int column;
}

为什么要有 line 和 column?

为了报错更清楚,比如:

JSON 解析错误:第 3 行,第 15 列,期望 ':',实际遇到 ','

这比单纯抛出:

Parse error

教学效果更好。

阶段三:实现词法分析器 Lexer

目标

把 JSON 字符串转换成 Token 列表。

示例

输入:

{"name":"周鑫","age":22}

输出:

LEFT_BRACE    {
STRING        name
COLON         :
STRING        周鑫
COMMA         ,
STRING        age
COLON         :
NUMBER        22
RIGHT_BRACE   }
EOF

Lexer 要实现的功能

重点方法

public List<Token> tokenize(String json);

private Token readString();

private Token readNumber();

private Token readTrue();

private Token readFalse();

private Token readNull();

阶段验收

能够把下面 JSON 正确拆分成 Token:

{
  "name": "周鑫",
  "age": 22,
  "active": true,
  "tags": ["Java", "JSON"]
}

阶段四:实现 JSON 数据模型

目标

不要一上来就转 JavaBean,先设计自己的 JSON 数据结构。

类结构设计

JsonValue
 ├── JsonObject
 ├── JsonArray
 ├── JsonString
 ├── JsonNumber
 ├── JsonBoolean
 └── JsonNull

JsonObject

public class JsonObject extends JsonValue {
    private Map<String, JsonValue> values = new LinkedHashMap<>();

    public JsonValue get(String key);
    public String getString(String key);
    public Integer getInteger(String key);
    public Double getDouble(String key);
    public Boolean getBoolean(String key);
    public JsonObject getObject(String key);
    public JsonArray getArray(String key);
}

建议用 LinkedHashMap,因为它可以保留 JSON 字段原始顺序。

JsonArray

public class JsonArray extends JsonValue {
    private List<JsonValue> values = new ArrayList<>();

    public JsonValue get(int index);
    public String getString(int index);
    public JsonObject getObject(int index);
    public int size();
}

阶段五:实现语法分析器 Parser

目标

把 Token 列表转换成 JsonObject / JsonArray。

核心思想

使用递归下降解析。

parseValue()
    如果当前 Token 是 {
        parseObject()
    如果当前 Token 是 [
        parseArray()
    如果当前 Token 是 STRING
        parseString()
    如果当前 Token 是 NUMBER
        parseNumber()
    如果当前 Token 是 TRUE / FALSE
        parseBoolean()
    如果当前 Token 是 NULL
        parseNull()

parseObject 逻辑

1. 读取 {
2. 判断是否直接遇到 }
3. 读取 key,必须是字符串
4. 读取 :
5. 读取 value
6. 如果遇到 ,,继续读取下一个 key-value
7. 如果遇到 },对象结束

parseArray 逻辑

1. 读取 [
2. 判断是否直接遇到 ]
3. 读取 value
4. 如果遇到 ,,继续读取下一个 value
5. 如果遇到 ],数组结束

示例解析过程

输入:

{"user":{"name":"周鑫","age":22}}

解析过程:

parseObject
  key = user
  value = parseObject
      key = name
      value = JsonString("周鑫")
      key = age
      value = JsonNumber(22)

阶段验收

支持下面这种多层嵌套:

JsonObject obj = ZXJson.parseObject(json);

String name = obj
    .getObject("data")
    .getObject("user")
    .getString("name");

阶段六:实现 JavaBean 映射

目标

实现类似 FastJSON 的基础能力:

User user = ZXJson.parseObject(json, User.class);

第二篇参考文章中的 JsonUtils 也围绕 toJson(Object)fromJson(String, Class&lt;T&gt;)toMap(...)、格式化 JSON 等工具方法展开,这可以作为你后续 API 设计的参考。(CSDN博客)

需要用到的 JavaSE 技术

示例 JavaBean

public class User {
    private Integer id;
    private String name;
    private Boolean active;
}

映射逻辑

1. JSON 字符串解析成 JsonObject
2. 通过 clazz.getDeclaredConstructor() 创建对象
3. 遍历 clazz.getDeclaredFields()
4. 根据字段名从 JsonObject 中取值
5. 根据字段类型进行转换
6. field.setAccessible(true)
7. field.set(obj, value)
8. 返回 JavaBean

支持字段类型

第一版只支持:

String
Integer / int
Long / long
Double / double
Boolean / boolean
自定义对象
List<String>
List<Integer>
List<自定义对象>

阶段七:实现对象转 JSON 字符串

目标

实现:

String json = ZXJson.toJSONString(user);

支持类型

示例

Java 对象:

User user = new User();
user.setId(1);
user.setName("周鑫");
user.setActive(true);

输出:

{"id":1,"name":"周鑫","active":true}

阶段八:实现注解增强

目标

模仿 FastJSON / Jackson 的字段控制能力。

自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface JsonField {
    String name() default "";
    boolean serialize() default true;
    boolean deserialize() default true;
}

示例

public class User {

    @JsonField(name = "user_name")
    private String name;

    @JsonField(serialize = false)
    private String password;
}

效果

{
  "user_name": "周鑫"
}

阶段九:实现格式化输出

目标

实现:

String prettyJson = ZXJson.toPrettyJSONString(obj);

普通输出

{"id":1,"name":"周鑫","roles":["admin","teacher"]}

格式化输出

{
  "id": 1,
  "name": "周鑫",
  "roles": [
    "admin",
    "teacher"
  ]
}

可实现功能


五、推荐项目结构

zx-json
├── src
│   └── main
│       └── java
│           └── com.zx.json
│               ├── ZXJson.java
│               ├── lexer
│               │   ├── JsonLexer.java
│               │   ├── Token.java
│               │   └── TokenType.java
│               ├── parser
│               │   └── JsonParser.java
│               ├── model
│               │   ├── JsonValue.java
│               │   ├── JsonObject.java
│               │   ├── JsonArray.java
│               │   ├── JsonString.java
│               │   ├── JsonNumber.java
│               │   ├── JsonBoolean.java
│               │   └── JsonNull.java
│               ├── serializer
│               │   └── JsonSerializer.java
│               ├── mapper
│               │   └── BeanMapper.java
│               ├── annotation
│               │   └── JsonField.java
│               └── exception
│                   ├── JsonParseException.java
│                   └── JsonMappingException.java
└── test
    └── java
        └── com.zx.json
            ├── LexerTest.java
            ├── ParserTest.java
            ├── BeanMapperTest.java
            └── SerializerTest.java

六、核心 API 设计

门面类 ZXJson

public class ZXJson {

    public static JsonValue parse(String json);

    public static JsonObject parseObject(String json);

    public static JsonArray parseArray(String json);

    public static <T> T parseObject(String json, Class<T> clazz);

    public static String toJSONString(Object object);

    public static String toPrettyJSONString(Object object);
}

使用示例

String json = "{\"name\":\"周鑫\",\"age\":22}";

JsonObject obj = ZXJson.parseObject(json);

System.out.println(obj.getString("name"));
System.out.println(obj.getInteger("age"));

JavaBean 示例

User user = ZXJson.parseObject(json, User.class);

System.out.println(user.getName());

七、阶段任务安排

第 1 阶段:JSON 基础与 Token 设计

任务

  1. 学习 JSON 基本语法;
  2. 设计 TokenType
  3. 设计 Token
  4. 手动分析几个 JSON 示例的 Token 序列。

交付物

TokenType.java
Token.java
JSON语法说明.md

第 2 阶段:词法分析器 Lexer

任务

  1. 实现字符读取;
  2. 实现字符串识别;
  3. 实现数字识别;
  4. 实现 true / false / null 识别;
  5. 实现非法字符报错。

交付物

JsonLexer.java
LexerTest.java

验收标准

输入:

{"name":"周鑫","age":22}

可以输出 Token 列表。


第 3 阶段:JsonObject 和 JsonArray

任务

  1. 设计 JsonValue 抽象类;
  2. 实现 JsonObject
  3. 实现 JsonArray
  4. 实现基础 getter 方法。

交付物

JsonValue.java
JsonObject.java
JsonArray.java
JsonString.java
JsonNumber.java
JsonBoolean.java
JsonNull.java

第 4 阶段:语法分析器 Parser

任务

  1. 实现 parseValue()
  2. 实现 parseObject()
  3. 实现 parseArray()
  4. 支持对象和数组递归嵌套;
  5. 支持错误提示。

交付物

JsonParser.java
ParserTest.java

验收标准

支持:

{
  "user": {
    "name": "周鑫",
    "roles": ["admin", "teacher"]
  }
}

第 5 阶段:JavaBean 反序列化

任务

  1. 通过反射创建对象;
  2. 通过字段名匹配 JSON key;
  3. 支持基础类型转换;
  4. 支持嵌套对象;
  5. 支持 List 集合。

交付物

BeanMapper.java
JsonMappingException.java
BeanMapperTest.java

第 6 阶段:对象序列化

任务

  1. 支持基础类型转 JSON;
  2. 支持 JavaBean 转 JSON;
  3. 支持 List / Map 转 JSON;
  4. 支持数组转 JSON;
  5. 处理字符串转义。

交付物

JsonSerializer.java
SerializerTest.java

第 7 阶段:注解和格式化增强

任务

  1. 实现 @JsonField
  2. 支持字段别名;
  3. 支持忽略字段;
  4. 支持格式化输出;
  5. 支持紧凑输出。

交付物

JsonField.java
PrettyPrinter.java

第 8 阶段:项目完善与文档

任务

  1. 编写 README;
  2. 编写使用示例;
  3. 补充异常测试;
  4. 补充复杂 JSON 测试;
  5. 和 FastJSON 做 API 对比。

交付物

README.md
使用说明.md
测试报告.md

八、功能清单

基础功能

进阶功能


九、学生最终可以学到什么

这个项目非常适合 JavaSE 教学,因为它能串联很多基础知识:



十一、项目难点

字符串转义

比如:

{
  "text": "hello \"world\""
}

需要正确识别:

\"
\\
\n
\r
\t
\u4e2d

数字解析

需要支持:

123
-123
3.14
-3.14
1e10
1.5E-3

第一版可以先只支持整数和小数,科学计数法放到增强阶段。

嵌套递归

比如:

{
  "a": {
    "b": {
      "c": [1, 2, {"d": true}]
    }
  }
}

这类结构必须靠递归解析。

类型转换

JSON 中只有 number,但 Java 里有:

Integer
Long
Double
Float
BigDecimal

第一版建议:

整数默认 Integer / Long
小数默认 Double

后期再加 BigDecimal。


十二、最终验收标准

项目完成后,至少要支持这些调用:

String json = "{ \"name\": \"周鑫\", \"age\": 22 }";

JsonObject obj = ZXJson.parseObject(json);

System.out.println(obj.getString("name"));
System.out.println(obj.getInteger("age"));
User user = ZXJson.parseObject(json, User.class);
String jsonStr = ZXJson.toJSONString(user);
String pretty = ZXJson.toPrettyJSONString(user);