2015年3月26日星期四

Java Json Parser

最近由於在下要編寫一些小工具需解析 Json ,而在下較喜歡使用 Java
但發現 Java SE 預設的 API 並沒有 Json Parser ,需要 Java EE 才有預設 Json Parser

json.org 提供讓 Java 的 Json Parser ,但在下對其操作方式有點不明白,因此愚蠢地自己寫一個 Java 的 Json Parser

根據 json.org 定義,Json 的物件稱為 value 共有 7種 可視型態:
  • object
  • array
  • string
  • number
  • true
  • false
  • null

建立 JsonValue 的抽象類別
public abstract class JsonValue<T> {
    private final T value;
    public JsonValue(T value) {
        this.value = value;
    }
    public T getValue() {
        return this.value;
    }
}
使用 Java 的 Generic 功能讓子類別根據需要改變成不同類別

先處理 string, number, true, false, null
public class JsonString extends JsonValue<String> {
    public JsonString(String value) {
        super(value);
    }
}
public abstract class JsonNumber<T extends Number> extends JsonValue<Number> {
    public JsonNumber(Number value) {
        super(value);
    }
}
public class JsonInteger extends JsonNumber<Integer> {
    public JsonInteger(int value) {
        super(value);
    }
}
public class JsonFloat extends JsonNumber<Float> {
    public JsonFloat(float value) {
        super(value);
    }
}
借用 Number 的概念
public abstract class JsonBoolean extends JsonValue<Boolean> {
    public JsonBoolean(Boolean value) {
        super(value);
    }
}
public class JsonTrue extends JsonBoolean {
    public JsonTrue() {
        super(true);
    }
}
public class JsonFalse extends JsonBoolean {
    public JsonFalse() {
        super(false);
    }
}
public class JsonNull extends JsonBoolean {
    public JsonNull() {
        super(null);
    }
}
借用 Boolean 製作 JsonNull 類別

Json array 則是一個 value 的集合,而 array 自己也是集合之一
public class JsonArray extends JsonValue<JsonValue[]> {
    private ArrayList<JsonValue> values = new ArrayList<>();
    public JsonArray() {
        this(new JsonValue[0]);
    }
    public JsonArray(JsonValue[] value) {
        super(null);
        this.values.addAll(Arrays.asList(value));
    }
    public void addValue(JsonValue value) {
        this.values.add(value);
    }
    @Override
    public JsonValue[] getValue() {
        return this.values.toArray(new JsonValue[this.values.size()]);
    }
    public JsonValue getValue(int index) {
        return this.values.get(index);
    }
}

最後是 Json object
object 由 key 及 value 的配對組成
key 必須是 string ,而 object 自己也是 value 之一
自訂 Json attribute
public class JsonAttribute {
    private final String key;
    private final JsonValue value;
    public JsonAttribute(String key, JsonValue value) {
        this.key = key;
        this.value = value;
    }
    public String getKey() {
        return this.key;
    }
    public JsonValue getValue() {
        return this.value;
    }
}
public class JsonObject extends JsonValue<JsonAttribute[]> {
    private ArrayList<JsonAttribute> values = new ArrayList<>();
    public JsonObject() {
        this(new JsonAttribute[0]);
    }
    public JsonObject(JsonAttribute[] value) {
        super(null);
        this.values.addAll(Arrays.asList(value));
    }
    public void addValue(JsonAttribute value) {
        this.values.add(value);
    }
    @Override
    public JsonAttribute[] getValue() {
        return this.values.toArray(new JsonAttribute[this.values.size()]);
    }
    public JsonValue getValue(String key) {
        for (JsonAttribute value : values) {
            if (value.getKey().equals(key)) {
                return value.getValue();
            }
        }
        return null;
    }
}

由於 JsonArray 及 JsonObject 的結構問題,因此不能直接使用 JsonValue[] 及 JsonAttribute[]
必須額外設定為可擴充的陣列例如 ArrayList ,而傳回的功能亦需要覆寫

定義各種 Json value 後便需要解柝一組字串是否 Json
static Stack<String> extract(String json) throws Exception {
    json = json.trim();
    Stack<Character> bracketPairs = new Stack<>();
    Stack<String> strings = new Stack<>();
    StringBuilder tempString = new StringBuilder();
    boolean openEscape = false;
    boolean openString = false;
    int length = json.length();
    for (int i = 0; i < length; i++) {
        char c = json.charAt(i);
        tempString.append(c);
        switch (c) {
            case '{': {
                if (openString) {
                    openEscape = false;
                } else {
                    bracketPairs.push(c);
                    strings.push(tempString.toString().trim());
                    tempString.delete(0, tempString.length());
                }
            }
            break;
            case '}': {
                if (openString) {
                    openEscape = false;
                } else {
                    if (bracketPairs.peek() == '{') {
                        bracketPairs.pop();
                        String string = tempString.toString().trim();
                        int index = string.lastIndexOf('}');
                        String previous = string.substring(0, index).trim();
                        if (previous.length() > 0) {
                            strings.push(previous);
                        }
                        strings.push(string.substring(index).trim());
                        tempString.delete(0, tempString.length());
                    } else {
                        throw new Exception("Error Message");
                    }
                }
            }
            break;
            case '[': {
                if (openString) {
                    openEscape = false;
                } else {
                    bracketPairs.push(c);
                    strings.push(tempString.toString().trim());
                    tempString.delete(0, tempString.length());
                }
            }
            break;
            case ']': {
                if (openString) {
                    openEscape = false;
                } else {
                    if (bracketPairs.peek() == '[') {
                        bracketPairs.pop();
                        String string = tempString.toString().trim();
                        int index = string.lastIndexOf(']');
                        String previous = string.substring(0, index).trim();
                        if (previous.length() > 0) {
                            strings.push(previous);
                        }
                        strings.push(string.substring(index).trim());
                        tempString.delete(0, tempString.length());
                    } else {
                        throw new Exception("Error Message");
                    }
                }
            }
            break;
            case ':': {
                if (openString) {
                    openEscape = false;
                } else {
                    strings.push(tempString.toString().trim());
                    tempString.delete(0, tempString.length());
                }
            }
            break;
            case ',': {
                if (openString) {
                    openEscape = false;
                } else {
                    String string = tempString.toString().trim();
                    int index = string.lastIndexOf(',');
                    String previous = string.substring(0, index).trim();
                    if (previous.length() > 0) {
                        strings.push(previous);
                    }
                    strings.push(string.substring(index).trim());
                    tempString.delete(0, tempString.length());
                }
            }
            break;
            case '"': {
                if (openString) {
                    if (openEscape) {
                        openEscape = false;
                    } else {
                        strings.push(tempString.toString().trim());
                        tempString.delete(0, tempString.length());
                        openString = false;
                    }
                } else {
                    openString = true;
                }
            }
            break;
            case '\\': {
                if (openString) {
                    openEscape = !openEscape;
                }
            }
            break;
            default: {
                openEscape = false;
            }
        }
    }
    if (bracketPairs.empty()) {
        return strings;
    } else {
        throw new Exception("Error Message");
    }
}
extract 只將「{」、「}」、「[」、「]」、「:」、「,」、「"」、 string 、其他資料分割及「\」跳脫字元處理,但不檢查合理性

然後是判斷分割的資料屬於哪一種類型,還是不屬於任何類型的錯誤資料
null, true, false 最簡單
public static JsonNull parseJsonNull(String json) throws Exception {
    json = json.trim();
    if (json.equals("null")) {
        return new JsonNull();
    } else {
        throw new Exception("Error Message");
    }
}
public static JsonTrue parseJsonTrue(String json) throws Exception {
    json = json.trim();
    if (json.equals("true")) {
        return new JsonTrue();
    } else {
        throw new Exception("Error Message");
    }
}
public static JsonFalse parseJsonFalse(String json) throws Exception {
    json = json.trim();
    if (json.equals("false")) {
        return new JsonFalse();
    } else {
        throw new Exception("Error Message");
    }
}
number 其實都比較繁複,整數、負數、浮點數、指數,但由於 Java 提供 number parser 的功能,借用 number parser 便可以輕鬆處理
public static JsonNumber parseJsonInteger(String json) throws Exception {
    json = json.trim();
    try {
        Double value = Double.parseDouble(json);
        if (value % 1.0 == 0.0) {
            return new JsonInteger(value.intValue());
        } else {
            throw new Exception("Error Message");
        }
    } catch (Exception ex) {
        throw new Exception("Error Message");
    }
}
public static JsonFloat parseJsonFloat(String json) throws Exception {
    json = json.trim();
    try {
        Double value = Double.parseDouble(json);
        if (value % 1.0 != 0.0) {
            return new JsonFloat(value.floatValue());
        } else {
            throw new Exception("Error Message");
        }
    } catch (Exception ex) {
        throw new Exception("Error Message");
    }
}
若果閣下對數值資料不用區分 integer 或 float ,直接傳回 double 作 JsonNumber 亦可以

string 與剛才的 extract 功能差不多,同樣需要分個字元檢查資料的正確
static final char CHAR_NULL = '\0';
static final char CHAR_BELL = '\7';
static final char CHAR_BACKSPACE = '\b';
static final char CHAR_LINE_FEED = '\n';
static final char CHAR_TAB = '\t';
static final char CHAR_VERTICAL_TAB = '\11';
static final char CHAR_FORM_FEED = '\f';
static final char CHAR_CARRIAGE_RETURN = '\r';
static final char CHAR_ESCAPE = '\27';

public static JsonString parseJsonString(String json) throws Exception {
    json = json.trim();
    if (json.length() < 2 || json.charAt(0) != '"' || json.charAt(json.length() - 1) != '"') {
        throw new Exception("Error Message");
    }
    json = json.substring(1, json.length() - 1);
    if (json.contains(Character.toString(CHAR_NULL))
        || json.contains(Character.toString(CHAR_BELL))
        || json.contains(Character.toString(CHAR_BACKSPACE))
        || json.contains(Character.toString(CHAR_LINE_FEED))
        || json.contains(Character.toString(CHAR_VERTICAL_TAB))
        || json.contains(Character.toString(CHAR_FORM_FEED))
        || json.contains(Character.toString(CHAR_CARRIAGE_RETURN))
        || json.contains(Character.toString(CHAR_ESCAPE))) {
        throw new Exception("Error Message");
    }
    StringBuilder tempString = new StringBuilder();
    boolean openEscape = false;
    int length = json.length();
    for (int i = 0; i < length; i++) {
        char c = json.charAt(i);
        switch (c) {
            case 'b': {
                if (openEscape) {
                    int tempLength = tempString.length();
                    if (tempLength > 0) {
                        tempString.delete(tempLength - 1, tempLength);
                    } else {
                        throw new Exception("Error Message");
                    }
                } else {
                    tempString.append(c);
                }
                openEscape = false;
            }
            break;
            case 'f': {
                if (openEscape) {
                    tempString.append('\f');
                } else {
                    tempString.append(c);
                }
                openEscape = false;
            }
            break;
            case 'n': {
                if (openEscape) {
                    tempString.append('\n');
                } else {
                    tempString.append(c);
                }
                openEscape = false;
            }
            break;
            case 'r': {
                if (openEscape) {
                    tempString.append('\r');
                } else {
                    tempString.append(c);
                }
                openEscape = false;
            }
            break;
            case 't': {
                if (openEscape) {
                    tempString.append('\t');
                } else {
                    tempString.append(c);
                }
                openEscape = false;
            }
            break;
            case 'u': {
                if (openEscape) {
                    try {
                        String hexString = json.substring(i + 1, i + 5);
                        tempString.append((char) Integer.parseInt(hexString, 16));
                        i += 4;
                    } catch (Exception ex) {
                        throw new Exception("Error Message");
                    }
                } else {
                    tempString.append(c);
                }
                openEscape = false;
            }
            break;
            case '"': {
                if (openEscape) {
                    tempString.append(c);
                    openEscape = false;
                } else {
                    throw new Exception("Error Message");
                }
            }
            break;
            case '\\': {
                if (openEscape) {
                    tempString.append(c);
                }
                openEscape = !openEscape;
            }
            break;
            default: {
                tempString.append(c);
                openEscape = false;
            }
        }
    }
    json = tempString.toString();
    return new JsonString(json);
}

由於 string, number, true, false, null 為獨立型態,以一個 method 整合處理
static JsonValue parseJsonOther(String string) {
    JsonValue jsonValue = null;
    if (jsonValue == null) {
        try {
            jsonValue = parseJsonString(string);
        } catch (Exception ex) {
        }
    }
    if (jsonValue == null) {
        try {
            jsonValue = parseJsonFloat(string);
        } catch (Exception ex) {
        }
    }
    if (jsonValue == null) {
        try {
            jsonValue = parseJsonInteger(string);
        } catch (Exception ex) {
        }
    }
    if (jsonValue == null) {
        try {
            jsonValue = parseJsonFalse(string);
        } catch (Exception ex) {
        }
    }
    if (jsonValue == null) {
        try {
            jsonValue = parseJsonTrue(string);
        } catch (Exception ex) {
        }
    }
    if (jsonValue == null) {
        try {
            jsonValue = parseJsonNull(string);
        } catch (Exception ex) {
        }
    }
    return jsonValue;
}

array 為一連串 Json Value 及 , 所組成
  • 開啟 array 後,必須為 Json Value 或 ]
  • 加入 Json Value 後,必須為 , 或 ]
  • , 後,必須為 Json Value
共有 3種狀態
static final int JSON_STATE_OPEN = 0;
static final int JSON_STATE_VALUE = JSON_STATE_OPEN + 1;
static final int JSON_STATE_COMMA = JSON_STATE_VALUE + 1;

static JsonArray parseJsonArray(Stack<String> strings, JsonArray jsonArray) throws Exception {
    if (strings.size() > 0) {
        int state = JSON_STATE_OPEN;
        while (strings.size() > 0) {
            String string = strings.remove(0);
            if (string.equals("[")) {
                if (state == JSON_STATE_OPEN || state == JSON_STATE_COMMA) {
                    jsonArray.addValue(parseJsonArray(strings, new JsonArray()));
                    state = JSON_STATE_VALUE;
                } else {
                    throw new Exception("Error Message");
                }
            } else if (string.equals("]")) {
                if (state == JSON_STATE_OPEN || state == JSON_STATE_VALUE) {
                    break;
                } else {
                    throw new Exception("Error Message");
                }
            } else if (string.equals("{")) {
                if (state == JSON_STATE_OPEN || state == JSON_STATE_COMMA) {
                    jsonArray.addValue(parseJsonObject(strings, new JsonObject()));
                    state = JSON_STATE_VALUE;
                } else {
                    throw new Exception("Error Message");
                }
            } else if (string.equals(",")) {
                if (state == JSON_STATE_VALUE) {
                    state = JSON_STATE_COMMA;
                } else {
                    throw new Exception("Error Message");
                }
            } else {
                if (state == JSON_STATE_OPEN || state == JSON_STATE_COMMA) {
                    JsonValue jsonValue = parseJsonOther(string);
                    if (jsonValue == null) {
                        throw new Exception("Error Message");
                    } else {
                        jsonArray.addValue(jsonValue);
                        state = JSON_STATE_VALUE;
                    }
                } else {
                    throw new Exception("Error Message");
                }
            }
        }
        return jsonArray;
    } else {
        return null;
    }
}

object 為一連串 key 、 : 、 Json Value 及 , 所組成
  • 開啟 object 後,必須為 key 或 }
  • key 後,必須為 :
  • : 後,必須為 Json Value
  • 加入 Json Value 後,必須為 , 或 }
  • , 後,必須為 key
共有 5種狀態
static final int JSON_STATE_OPEN = 0;
static final int JSON_STATE_KEY = JSON_STATE_OPEN + 1;
static final int JSON_STATE_COLON = JSON_STATE_KEY + 1;
static final int JSON_STATE_VALUE = JSON_STATE_COLON + 1;
static final int JSON_STATE_COMMA = JSON_STATE_VALUE + 1;

static JsonObject parseJsonObject(Stack<String> strings, JsonObject jsonObject) throws Exception {
    if (strings.size() > 0) {
        String key = null;
        int state = JSON_STATE_OPEN;
        while (strings.size() > 0) {
            String string = strings.remove(0);
            if (string.equals("{")) {
                if (state == JSON_STATE_COLON) {
                    jsonObject.addValue(new JsonAttribute(key, parseJsonObject(strings, new JsonObject())));
                    state = JSON_STATE_VALUE;
                } else {
                    throw new Exception("Error Message");
                }
            } else if (string.equals("}")) {
                if (state == JSON_STATE_OPEN || state == JSON_STATE_VALUE) {
                    break;
                } else {
                    throw new Exception("Error Message");
                }
            } else if (string.equals("[")) {
                if (state == JSON_STATE_COLON) {
                    jsonObject.addValue(new JsonAttribute(key, parseJsonArray(strings, new JsonArray())));
                    state = JSON_STATE_VALUE;
                } else {
                    throw new Exception("Error Message");
                }
            } else if (string.equals(":")) {
                if (state == JSON_STATE_KEY) {
                    state = JSON_STATE_COLON;
                } else {
                    throw new Exception("Error Message");
                }
            } else if (string.equals(",")) {
                if (state == JSON_STATE_VALUE) {
                    state = JSON_STATE_COMMA;
                } else {
                    throw new Exception("Error Message");
                }
            } else {
                if (state == JSON_STATE_OPEN || state == JSON_STATE_COMMA) {
                    try {
                        key = parseJsonString(string).getValue();
                        state = JSON_STATE_KEY;
                    } catch (Exception ex) {
                        throw new Exception("Error Message");
                    }
                } else if (state == JSON_STATE_COLON) {
                    JsonValue jsonValue = parseJsonOther(string);
                    if (jsonValue == null) {
                        throw new Exception("Error Message");
                    } else {
                        jsonObject.addValue(new JsonAttribute(key, jsonValue));
                        state = JSON_STATE_VALUE;
                    }
                } else {
                    throw new Exception("Error Message");
                }
            }
        }
        return jsonObject;
    } else {
        return null;
    }
}

最後整合所有功能
public static JsonValue parse(String json) throws Exception {
    Stack<String> strings = extract(json);
    if (strings.empty()) {
        throw new Exception("Error Message");
    } else {
        String string = strings.remove(0);
        if (string.equals("{")) {
            return parseJsonObject(strings, new JsonObject());
        } else if (string.equals("[")) {
            return parseJsonArray(strings, new JsonArray());
        } else {
            throw new Exception("Error Message");
        }
    }
}
由於 Json 必須以 { 或 [ 作初始,若字串清單的第一個字串不是 { 或 [ 或為錯誤

另外由於使用了 Stack, ArrayList, Arrays 等類別,因此需要載入 java.util.*
在下以 Json @ WikiGoogle Map API Json 格式 檢查正常
以 Json @ Wiki 為例
{
  "firstName": "John",
  "lastName": "Smith",
  "isAlive": true,
  "age": 25,
  "height_cm": 167.6,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "New York",
    "state": "NY",
    "postalCode": "10021-3100"
  },
  "phoneNumbers": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "office",
      "number": "646 555-4567"
    }
  ],
  "children": [],
  "spouse": null
}
JsonObject jsonObject = (JsonObject) parse(s);
System.out.println(((JsonString) jsonObject.getValue("firstName")).getValue());
System.out.println(((JsonBoolean) jsonObject.getValue("isAlive")).getValue());
System.out.println(((JsonNumber) jsonObject.getValue("age")).getValue());
System.out.println(((JsonNumber) jsonObject.getValue("height_cm")).getValue());
System.out.println(((JsonString) ((JsonObject) jsonObject.getValue("address")).getValue("streetAddress")).getValue());
System.out.println(((JsonString) ((JsonObject) ((JsonArray) jsonObject.getValue("phoneNumbers")).getValue(0)).getValue("type")).getValue());
System.out.println(((JsonBoolean) jsonObject.getValue("spouse")).getValue());
輸出成
John
true
25
167.6
21 2nd Street
home
null
避免資料發生 ClassCaseException 可以先用 instanceof 來檢查傳回資料能否轉型

同一個 Json object 的 Json attribute 的 key 重覆,會以新的 Json attribute 取代
由於在下使用 ArrayList 作為陣列又忽略了檢查重覆,會發生重覆 Json attribute
其實應該使用 HashMap 會比較適合,讓 Map 自動處理重覆的 key ,而使用 ArrayList 則需要由使用者自行處理

沒有留言 :

發佈留言