處理動態 JSON 結構是後端開發常見需求:第三方 API 回傳欄位不固定、配置檔結構多變、或需將 JSON 統一轉為 Map/List 供通用邏輯處理。傳統做法需針對每種結構寫死對應的 POJO,維護成本高。本文介紹一套基於 org.json 與正則表達式的遞迴解析器,能自動處理任意層級的 JSONObject 與 JSONArray,輸出巢狀的 Map<String, Object>、List<Object> 或基本型別。
定義 RegexPolicy 列舉,預留擴充點(如 HTML 圖片擷取、Email 提取):
public enum RegexPolicy {
Json("Json"),
Image("ImageFromHtml");
private final String value;
RegexPolicy(String value) { this.value = value; }
@Override public String toString() { return value; }
}
regexJson() 為核心遞迴函式,流程如下:
JSONObject 或 JSONArray,失敗則視為純量值直接回傳。"([^\" ]+?):" 擷取所有鍵名,配合 JSONObject.opt() 取值,對值遞迴呼叫 regexJson()。JSONArray,每個元素遞迴解析後加入 List<Object>。Matcher.region() 避免重複匹配。public static Object regex(final RegexPolicy policy, final String data) {
switch (policy) {
case Json: return regexJson(data);
case Image: break; // 預留擴充
default: break;
}
return null;
}
private static Object regexJson(final String jsonStr) {
if (jsonStr == null) throw new NullPointerException("JsonString shouldn't be null");
try {
if (isJsonObject(jsonStr)) {
// 解析 JSONObject -> Map
final Pattern pattern = Pattern.compile(REGULAR_JSON_ITEM_NAME);
final Matcher matcher = pattern.matcher(jsonStr);
final Map map = new HashMap<>();
final JSONObject jsonObject = new JSONObject(jsonStr);
for (; matcher.find(); ) {
String key = matcher.group(1);
Object value = jsonObject.opt(key);
// 跳過巢狀陣列字串長度,避免正則重複匹配內部鍵名
if (value != null && isJsonArray(value.toString())) {
matcher.region(matcher.end() + value.toString().replace("\\", "").length(), matcher.regionEnd());
}
if (value != null && !map.containsKey(key)) {
map.put(key, regexJson(value.toString()));
}
}
return map;
} else if (isJsonArray(jsonStr)) {
// 解析 JSONArray -> List
private static boolean isJsonObject(final String jsonStr) {
if (jsonStr == null) return false;
try { new JSONObject(jsonStr); return true; }
catch (JSONException e) { return false; }
}
private static boolean isJsonArray(final String jsonStr) {
if (jsonStr == null) return false;
try { new JSONArray(jsonStr); return true; }
catch (JSONException e) { return false; }
}
// 假設 jsonStr 為完整 JSON 字串,可能包含前後雜訊
String pureJson = jsonStr.substring(jsonStr.indexOf("{"), jsonStr.lastIndexOf("}") + 1);
Object result = RegexUtil.regex(RegexPolicy.Json, pureJson);
if (result instanceof String) {
Log.e("scalar", result.toString());
} else if (result instanceof Map) {
@SuppressWarnings("unchecked")
Map map = (Map) result;
for (Map.Entry entry : map.entrySet()) {
Log.e(entry.getKey(), entry.getValue().toString());
}
} else if (result instanceof List) {
Log.e("list", result.toString());
}
| 優點 | 注意事項 |
|---|---|
| 無需定義 POJO,動態結構自適應 | 大量資料時效能遜於 Jackson/Gson 流式解析 |
| 統一輸出 Map/List,便於通用邏輯處理 | 正則匹配鍵名假設無空格、無特殊字元,極端情況可能遺漏 |
依賴輕量 org.json,無額外重依賴 |
日期、二進位等特殊型別會序列化為字串,需自行後處理 |
Jackson JsonNode 替代 org.json,支援更完整的 JSON 語法(如尾隨逗號、註解)。TypeReference 機制,解析時自動轉換目標型別(LocalDate、BigDecimal 等)。這套遞迴解析器以「約定優於配置」的方式,將任意層級 JSON 統一映射為 Java 集合結構,極大降低動態資料處理的樣板程式碼。雖非高效能場景首選,但在配置解析、API 閘道轉發、測試資料生成等場景極具實用價值。