/************************************************************************ * @Mdodified by G0nzo * @description: JSON格式字符串序列化和反序列化, 修改自[HotKeyIt/Yaml](https://github.com/HotKeyIt/Yaml) * 增加了对true/false/null类型的支持, 保留了数值的类型 * @author thqby, HotKeyIt * @date 2023/05/12 * @version 1.0.5 ***********************************************************************/ class JSON { static null := ComValue(1, 0), true := ComValue(0xB, 1), false := ComValue(0xB, 0) /** * Converts a AutoHotkey Object Notation JSON string into an object. * @param text A valid JSON string. * @param keepbooltype convert true/false/null to JSON.true / JSON.false / JSON.null where it's true, otherwise 1 / 0 / '' * @param as_map object literals are converted to map, otherwise to object */ static Load(text, keepbooltype := false, as_map := true) { keepbooltype ? (_true := JSON.true, _false := JSON.false, _null := JSON.null) : (_true := true, _false := false, _null := "") as_map ? (map_set := (maptype := Map).Prototype.Set) : (map_set := (obj, key, val) => obj.%key% := val, maptype := Object) NQ := "", LF := "", LP := 0, P := "", R := "" D := [C := (A := InStr(text := LTrim(text, " `t`r`n"), "[") = 1) ? [] : maptype()], text := LTrim(SubStr(text, 2), " `t`r`n"), L := 1, N := 0, V := K := "", J := C, !(Q := InStr(text, '"') != 1) ? text := LTrim(text, '"') : "" Loop Parse text, '"' { Q := NQ ? 1 : !Q NQ := Q && (SubStr(A_LoopField, -3) = "\\\" || (SubStr(A_LoopField, -1) = "\" && SubStr(A_LoopField, -2) != "\\")) if !Q { if (t := Trim(A_LoopField, " `t`r`n")) = "," || (t = ":" && V := 1) continue else if t && (InStr("{[]},:", SubStr(t, 1, 1)) || RegExMatch(t, "^-?\d*(\.\d*)?\s*[,\]\}]")) { Loop Parse t { if N && N-- continue if InStr("`n`r `t", A_LoopField) continue else if InStr("{[", A_LoopField) { if !A && !V throw Error("Malformed JSON - missing key.", 0, t) C := A_LoopField = "[" ? [] : maptype(), A ? D[L].Push(C) : map_set(D[L], K, C), D.Has(++L) ? D[L] := C : D.Push(C), V := "", A := Type(C) = "Array" continue } else if InStr("]}", A_LoopField) { if !A && V throw Error("Malformed JSON - missing value.", 0, t) else if L = 0 throw Error("Malformed JSON - to many closing brackets.", 0, t) else C := --L = 0 ? "" : D[L], A := Type(C) = "Array" } else if !(InStr(" `t`r,", A_LoopField) || (A_LoopField = ":" && V := 1)) { if RegExMatch(SubStr(t, A_Index), "m)^(null|false|true|-?\d+\.?\d*)\s*[,}\]\r\n]", &R) && (N := R.Len(0) - 2, R := R.1, 1) { if A C.Push(R = "null" ? _null : R = "true" ? _true : R = "false" ? _false : IsNumber(R) ? R + 0 : R) else if V map_set(C, K, R = "null" ? _null : R = "true" ? _true : R = "false" ? _false : IsNumber(R) ? R + 0 : R), K := V := "" else throw Error("Malformed JSON - missing key.", 0, t) } else { ; Added support for comments without '"' if A_LoopField == '/' { nt := SubStr(t, A_Index + 1, 1), N := 0 if nt == '/' { if nt := InStr(t, '`n', , A_Index + 2) N := nt - A_Index - 1 } else if nt == '*' { if nt := InStr(t, '*/', , A_Index + 2) N := nt + 1 - A_Index } else nt := 0 if N continue } throw Error("Malformed JSON - unrecognized character-", 0, A_LoopField " in " t) } } } } else if InStr(t, ':') > 1 throw Error("Malformed JSON - unrecognized character-", 0, SubStr(t, 1, 1) " in " t) } else if NQ && (P .= A_LoopField '"', 1) continue else if A LF := P A_LoopField, C.Push(InStr(LF, "\") ? UC(LF) : LF), P := "" else if V LF := P A_LoopField, map_set(C, K, InStr(LF, "\") ? UC(LF) : LF), K := V := P := "" else LF := P A_LoopField, K := InStr(LF, "\") ? UC(LF) : LF, P := "" } return J UC(S, e := 1) { static m := Map(Ord('"'), '"', Ord("a"), "`a", Ord("b"), "`b", Ord("t"), "`t", Ord("n"), "`n", Ord("v"), "`v", Ord("f"), "`f", Ord("r"), "`r") local v := "" Loop Parse S, "\" if !((e := !e) && A_LoopField = "" ? v .= "\" : !e ? (v .= A_LoopField, 1) : 0) v .= (t := InStr("ux", SubStr(A_LoopField, 1, 1)) ? SubStr(A_LoopField, 1, RegExMatch(A_LoopField, "i)^[ux]?([\dA-F]{4})?([\dA-F]{2})?\K") - 1) : "") && RegexMatch(t, "i)^[ux][\da-f]+$") ? Chr(Abs("0x" SubStr(t, 2))) SubStr(A_LoopField, RegExMatch(A_LoopField, "i)^[ux]?([\dA-F]{4})?([\dA-F]{2})?\K")) : m.has(Ord(A_LoopField)) ? m[Ord(A_LoopField)] SubStr(A_LoopField, 2) : "\" A_LoopField, e := A_LoopField = "" ? e : !e return v } } /** * Converts a AutoHotkey Array/Map/Object to a Object Notation JSON string. * @param obj A AutoHotkey value, usually an object or array or map, to be converted. * @param expandlevel The level of JSON string need to expand, by default expand all. * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. */ static Dump(obj, expandlevel := unset, space := " ") { expandlevel := IsSet(expandlevel) ? Abs(expandlevel) : 10000000 return Trim(CO(obj, expandlevel)) CO(O, J := 0, R := 0, Q := 0) { static M1 := "{", M2 := "}", S1 := "[", S2 := "]", N := "`n", C := ",", S := "- ", E := "", K := ":" if (OT := Type(O)) = "Array" { D := !R ? S1 : "" for key, value in O { F := (VT := Type(value)) = "Array" ? "S" : InStr("Map,Object", VT) ? "M" : E Z := VT = "Array" && value.Length = 0 ? "[]" : ((VT = "Map" && value.count = 0) || (VT = "Object" && ObjOwnPropCount(value) = 0)) ? "{}" : "" D .= (J > R ? "`n" CL(R + 2) : "") (F ? (%F%1 (Z ? "" : CO(value, J, R + 1, F)) %F%2) : ES(value)) (OT = "Array" && O.Length = A_Index ? E : C) } } else { D := !R ? M1 : "" for key, value in (OT := Type(O)) = "Map" ? (Y := 1, O) : (Y := 0, O.OwnProps()) { F := (VT := Type(value)) = "Array" ? "S" : InStr("Map,Object", VT) ? "M" : E Z := VT = "Array" && value.Length = 0 ? "[]" : ((VT = "Map" && value.count = 0) || (VT = "Object" && ObjOwnPropCount(value) = 0)) ? "{}" : "" D .= (J > R ? "`n" CL(R + 2) : "") (Q = "S" && A_Index = 1 ? M1 : E) ES(key) K (F ? (%F%1 (Z ? "" : CO(value, J, R + 1, F)) %F%2) : ES(value)) (Q = "S" && A_Index = (Y ? O.count : ObjOwnPropCount(O)) ? M2 : E) (J != 0 || R ? (A_Index = (Y ? O.count : ObjOwnPropCount(O)) ? E : C) : E) if J = 0 && !R D .= (A_Index < (Y ? O.count : ObjOwnPropCount(O)) ? C : E) } } if J > R D .= "`n" CL(R + 1) if R = 0 D := RegExReplace(D, "^\R+") (OT = "Array" ? S2 : M2) return D } ES(S) { switch Type(S) { case "Float": if (v := '', d := InStr(S, 'e')) v := SubStr(S, d), S := SubStr(S, 1, d - 1) if ((StrLen(S) > 17) && (d := RegExMatch(S, "(99999+|00000+)\d{0,3}$"))) S := Round(S, Max(1, d - InStr(S, ".") - 1)) return S v case "Integer": return S case "String": S := StrReplace(S, "\", "\\") S := StrReplace(S, "`t", "\t") S := StrReplace(S, "`r", "\r") S := StrReplace(S, "`n", "\n") S := StrReplace(S, "`b", "\b") S := StrReplace(S, "`f", "\f") S := StrReplace(S, "`v", "\v") S := StrReplace(S, '"', '\"') return '"' S '"' default: return S == JSON.true ? "true" : S == JSON.false ? "false" : "null" } } CL(i) { Loop (s := "", space ? i - 1 : 0) s .= space return s } } }