首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

手寫 JSON 庫:從對象到 JSON 文本與反解析的遞歸實現

     編輯:Java 綜合教學

手寫 JSON 庫:從對象到 JSON 文本與反解析的遞歸實現

造輪子是理解技術本質的最佳途徑。本文實現一個**極簡 JSON 庫 (MyJSON)**,支持 **Bean/Map/List/Array/基本類型** 雙向轉換,核心邏輯僅靠**遞歸 + 反射 + 字符串流式處理**,代碼量 < 300 行,適合學習 JSON 序列化核心原理。

一、設計原則

  • **零依賴**:僅用 JDK 標準庫 (反射、集合、IO)
  • **遞歸下降**:對象圖層層遞歸,Map/List/Bean 統一入口
  • **流式輸出**:`StringBuffer` 累加,避免頻繁字符串拼接
  • **容錯優先**:解析階段詳細報錯位置、字符、結構異常
  • **不處理循環引用**:JSON 標準不支持,遇循環拋棄/死循環 (生產環境需檢測)

二、序列化:toJSON(Object) 核心遞歸結構

public static String toJSON(Object object) {
    StringBuffer json = new StringBuffer();
    if (object instanceof Map) {
        writeMap(json, object);
    } else if (object instanceof List || object.getClass().isArray()) {
        writeList(json, object);
    } else {
        writeBean(json, object); // 含基本類型包裝類
    }
    return json.toString();
}

// 1. Bean → { "field": value, ... }
private static void writeBean(StringBuffer json, Object obj) {
    Class<?> clazz = obj.getClass();
    Field[] fields = clazz.getDeclaredFields();
    int i = 0;
    for (Field field : fields) {
        try {
            PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);
            Method getter = pd.getReadMethod();
            Object value = getter.invoke(obj);
            if (i++ == 0) json.append('{'); else json.append(',');
            json.append('"').append(field.getName()).append("\":");
            writeValue(json, value); // 遞歸寫入值
        } catch (Exception ignored) {}
    }
    if (i == 0) json.append('"').append(obj.toString()).append('"'); // 非 Bean 直接 toString
    else json.append('}');
}

// 2. Map → { "key": value, ... }
private static void writeMap(StringBuffer json, Object obj) {
    Map<?,?> map = (Map<?,?>) obj;
    json.append('{');
    int i = 0;
    for (Map.Entry<?,?> entry : map.entrySet()) {
        if (entry.getKey() != null) {
            if (i++ != 0) json.append(',');
            json.append('"').append(entry.getKey().toString()).append("\":");
            writeValue(json, entry.getValue());
        }
    }
    json.append('}');
}

// 3. List/Array → [ value, ... ]
private static void writeList(StringBuffer json, Object obj) {
    List<?> list = obj.getClass().isArray() ? Arrays.asList((Object[]) obj) : (List<?>) obj;
    json.append('[');
    for (int i = 0; i < list.size(); i++) {
        if (i != 0) json.append(',');
        writeValue(json, list.get(i));
    }
    json.append(']');
}

// 4. 值類型分發
private static void writeValue(StringBuffer json, Object value) {
    if (value == null) { json.append("null"); return; }
    if (value instanceof String) json.append('"').append(escape(value.toString())).append('"');
    else if (value instanceof Integer) json.append(value);
    else if (value instanceof Boolean) json.append(value);
    else if (value instanceof Character) json.append('"').append(value).append('"');
    else if (value instanceof Long) json.append(value);
    else if (value instanceof Double) json.append(value);
    else if (value instanceof Float) json.append(value);
    else if (value instanceof Map) writeMap(json, value);
    else if (value instanceof List || value.getClass().isArray()) writeList(json, value);
    else writeBean(json, value); // 其它對象視為 Bean
}

// 5. 字符串轉義
private static String escape(String s) {
    return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t");
}

三、反序列化:fromJSON(String) 遞歸下降解析器

public static Object fromJSON(String json) {
    json = json.trim();
    if (json.startsWith("{")) return parseMap(json, new int[]{1});
    if (json.startsWith("[")) return parseList(json, new int[]{1});
    throw new IllegalArgumentException("JSON must start with { or [");
}

// 1. 解析 Map { "key": value, ... }
private static Map<String,Object> parseMap(String json, int[] pos) {
    Map<String,Object> map = new HashMap<>();
    while (pos[0] < json.length()) {
        skipBlanks(json, pos);
        if (json.charAt(pos[0]) == '}') { pos[0]++; break; } // 空 Map 或結束
        String key = parseString(json, pos); // 解析 "key"
        skipBlanks(json, pos);
        expectChar(json, pos, ':'); // 跳過 :
        Object value = parseValue(json, pos); // 遞歸解析值
        map.put(key, value);
        skipBlanks(json, pos);
        char c = json.charAt(pos[0]);
        if (c == '}') { pos[0]++; break; }
        if (c == ',') { pos[0]++; continue; }
        throw new IllegalArgumentException("Expect } or , at " + pos[0]);
    }
    return map;
}

// 2. 解析 List [ value, ... ]
private static List<Object> parseList(String json, int[] pos) {
    List<Object> list = new ArrayList<>();
    while (pos[0] < json.length()) {
        skipBlanks(json, pos);
        if (json.charAt(pos[0]) == ']') { pos[0]++; break; }
        Object value = parseValue(json, pos);
        list.add(value);
        skipBlanks(json, pos);
        char c = json.charAt(pos[0]);
        if (c == ']') { pos[0]++; break; }
        if (c == ',') { pos[0]++; continue; }
        throw new IllegalArgumentException("Expect ] or , at " + pos[0]);
    }
    return list;
}

// 3. 解析值:字符串/數字/布爾/null/Map/List
private static Object parseValue(String json, int[] pos) {
    skipBlanks(json, pos);
    char c = json.charAt(pos[0]);
    if (c == '"') return parseString(json, pos);
    if (c == '{') { pos[0]++; return parseMap(json, pos); }
    if (c == '[') { pos[0]++; return parseList(json, pos); }
    if (c == 't') { pos[0] += 4; return true; } // true
    if (c == 'f') { pos[0] += 5; return false; } // false
    if (c == 'n') { pos[0] += 4; return null; } // null
    // 數字解析
    int start = pos[0];
    while (pos[0] < json.length() && isNumberChar(json.charAt(pos[0]))) pos[0]++;
    String numStr = json.substring(start, pos[0]).trim();
    return parseNumber(numStr);
}

// 4. 解析字符串 (處理轉義)
private static String parseString(String json, int[] pos) {
    // pos[0] 指向開頭 "
    pos[0]++; // 跳過 "
    StringBuilder sb = new StringBuilder();
    while (pos[0] < json.length()) {
        char c = json.charAt(pos[0]++);
        if (c == '"') break; // 結束
        if (c == '\\') { // 轉義
            if (pos[0] >= json.length()) throw new IllegalArgumentException("Incomplete escape");
            char esc = json.charAt(pos[0]++);
            switch (esc) {
                case '"': sb.append('"'); break;
                case '\\': sb.append('\\'); break;
                case 'n': sb.append('\n'); break;
                case 'r': sb.append('\r'); break;
                case 't': sb.append('\t'); break;
                default: sb.append(esc); // 其它按原樣
            }
        } else {
            sb.append(c);
        }
    }
    return sb.toString();
}

// 5. 數字解析
private static Number parseNumber(String s) {
    if (s.contains(".") || s.contains("e") || s.contains("E")) {
        try { return Double.parseDouble(s); } catch (NumberFormatException e) { return Float.parseFloat(s); }
    } else {
        try { return Long.parseLong(s); } catch (NumberFormatException e) { return Integer.parseInt(s); }
    }
}

// 輔助
private static void skipBlanks(String json, int[] pos) {
    while (pos[0] < json.length() && " \t\r\n".indexOf(json.charAt(pos[0])) >= 0) pos[0]++;
}
private static void expectChar(String json, int[] pos, char expected) {
    skipBlanks(json, pos);
    if (pos[0] >= json.length() || json.charAt(pos[0]) != expected)
        throw new IllegalArgumentException("Expect '" + expected + "' at " + pos[0]);
    pos[0]++;
}
private static boolean isNumberChar(char c) {
    return "0123456789.-+eE".indexOf(c) >= 0;
}

四、使用示例

// 序列化
TestBean bean = new TestBean();
bean.setName("Stri\"ngTe[]}xt");
bean.setFlag(true);
bean.setCount(233);
bean.setRatio(3.14f);
Map<String,Object> map = new HashMap<>();
map.put("k1", "v1");
map.put("k2", 100);
bean.setMap(map);
bean.setList(Arrays.asList("a", "b", map));
System.out.println(MyJSON.toJSON(bean));
// 輸出: {"name":"Stri\"ngTe[]}xt","flag":true,"count":233,"ratio":3.14,"map":{"k1":"v1","k2":100},"list":["a","b",{"k1":"v1","k2":100}]}

// 反序列化
String json = "{\"users\":[{\"name\":\"A\",\"age\":20},{\"name\":\"B\",\"age\":25}]}";
Map<String,Object> parsed = (Map<String,Object>) MyJSON.fromJSON(json);
List<Object> users = (List<Object>) parsed.get("users");
users.forEach(u -> System.out.println(((Map<String,Object>)u).get("name")));

五、與成熟庫 (Jackson/Gson/FastJson) 的差距與反思

維度MyJSON (教學級)生產級庫
性能反射+字符串拼接,慢 10-100 倍字節碼生成/直接內存操作、對象池、SIMD 加速
功能基礎類型、Bean/Map/List流式 API、樹模型、日期/時間、多態、自定義序列化、Schema 驗證、YAML/CBOR 支持
健壯性簡單報錯、不處理循環引用、大數字精度丟失循環引用檢測、大數字 (BigDecimal)、嚴格/寬容模式、安全反序列化
易用性手動調用註解驅動、模塊化、Spring 集成、GraalVM 原生

六、總結

手寫 JSON 庫讓我們直觀理解:

  1. **序列化** = 對象圖遞歸遍歷 → 字符串流式輸出 (字段名加雙引號、值類型分發、特殊字符轉義)
  2. **反序列化** = 字符流遞歸下降解析 → 對象圖構建 (指針位置推進、上下文感知、類型推斷、轉義還原)
  3. **核心難點**:循環引用檢測、多態類型保留、大數字精度、流式處理內存控制、安全反序列化 (拒絕任意類實例化)

生產環境**堅決使用成熟庫**,但理解其內核原理,能讓你在排查序列化異常、優化 JSON 性能、設計自定義協議時游刃有餘。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved