Skip to content

Commit

Permalink
Json object mapper library
Browse files Browse the repository at this point in the history
  • Loading branch information
TheBestPessimist committed Aug 13, 2023
1 parent b468e0d commit c9cde51
Showing 1 changed file with 268 additions and 0 deletions.
268 changes: 268 additions & 0 deletions lib/_JXON.ahk
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
/*
TBP note start
Source: https://github.com/buliasz/JXON_ahk2/blob/master/_JXON.ahk
From: https://www.autohotkey.com/boards/viewtopic.php?f=83&t=74799&start=20
TBP note end
*/

;;;; AHK v2
; Example ===================================================================================
; ===========================================================================================

; Msgbox "The idea here is to create several nested arrays, save to text with JxonEncode(), and then reload the array with JxonDecode(). The resulting array should be the same.`r`n`r`nThis is what this example shows."

; a := Map(), b := Map(), c := Map(), d := Map(), e := Map(), f := Map()
; d["g"] := 1, d["h"] := 2, d["i"] := ["purple","pink","pippy red"]
; e["g"] := 1, e["h"] := 2, e["i"] := Map("1","test1","2","test2","3","test3")
; f["g"] := 1, f["h"] := 2, f["i"] := [1,2,Map("a",1.0009,"b",2.0003,"c",3.0001)]

; a["test1"] := "test11", a["d"] := d
; b["test3"] := "test33", b["e"] := e
; c["test5"] := "test55", c["f"] := f

; myObj := MyClass()
; myObj.a := a, myObj.b := b, myObj.c := c, myObj.test7 := "test77", myObj.test8 := "test88"

; g := ["blue","green","red"], myObj.h := g ; add linear array for testing

; textData2 := JxonEncode(myObj,4) ; ===> convert array to JSON
; msgbox "JSON output text:`r`n===========================================`r`n(Should match second output.)`r`n`r`n" textData2

; newObj := JxonDecode(&textData2) ; ===> convert json back to MyClass object

; textData3 := JxonEncode(newObj,4) ; ===> break down array into JSON string again, should be identical
; msgbox "Second output text:`r`n===========================================`r`n(should be identical to first output)`r`n`r`n" textData3

; ExitApp

; ===========================================================================================
; End Example ; =============================================================================
; ===========================================================================================

; originally posted by user coco on AutoHotkey.com
; https://github.com/cocobelgica/AutoHotkey-JSON

JxonDecode(&src, args*) {
key := "", isKey := false
stack := [ tree := [] ]
isArr := Map(tree, 1) ; ahk v2
next := '"{[01234567890-tfn'
pos := 0

while ( (ch := SubStr(src, ++pos, 1)) != "" ) {
if InStr(" `t`n`r", ch)
continue
if !InStr(next, ch, true) {
testArr := StrSplit(SubStr(src, 1, pos), "`n")

testArrLength := testArr.Length
col := pos - InStr(src, "`n",, -(StrLen(src)-pos+1))

msg := Format("{}: line {} col {} (char {})"
, (next == "") ? ["Extra data", ch := SubStr(src, pos)][1]
: (next == "'") ? "Unterminated string starting at"
: (next == "\") ? "Invalid \escape"
: (next == ":") ? "Expecting ':' delimiter"
: (next == '"') ? "Expecting object key enclosed in double quotes"
: (next == '"}') ? "Expecting object key enclosed in double quotes or object closing '}'"
: (next == ",}") ? "Expecting ',' delimiter or object closing '}'"
: (next == ",]") ? "Expecting ',' delimiter or array closing ']'"
: [ "Expecting JSON value(string, number, [true, false, null], object or array)"
, ch := SubStr(src, pos, (SubStr(src, pos)~="[\]\},\s]|$")-1) ][1]
, testArrLength, col, pos)

throw Error(msg, -1, ch)
}

obj := stack[1]
memType := Type(obj)
isArrayType := (memType = "Array") ? 1 : 0

if indx := InStr("{[", ch) { ; start new object/map or array
if (indx == 1) {
val := Map()
} else {
val := Array()
}

if (isArrayType) {
obj.Push(val)
} else {
obj[key] := val
}

stack.InsertAt(1,val)

isKey := (ch == "{")
isArr[val] := !isKey
next := '"' (isKey ? "}" : "{[]0123456789-tfn")
} else if InStr("}]", ch) { ; end object/map or array
stack.RemoveAt(1)
next := stack[1]==tree ? "" : isArr[stack[1]] ? ",]" : ",}"

} else if InStr(",:", ch) {
isKey := (!isArrayType && ch == ",")
next := isKey ? '"' : '"{[0123456789-tfn'

} else { ; string | number | true | false | null
if (ch == '"') { ; string
indx := pos
while indx := InStr(src, '"',, indx+1) {
val := StrReplace(SubStr(src, pos+1, indx-pos-1), "\\", "\u005C")
if (SubStr(val, -1) != "\")
break
}
if !indx ? (pos--, next := "'") : 0
continue

pos := indx ; update pos

val := StrReplace(val, "\/", "/")
val := StrReplace(val, '\"', '"')
val := StrReplace(val, "\b", "`b")
val := StrReplace(val, "\f", "`f")
val := StrReplace(val, "\n", "`n")
val := StrReplace(val, "\r", "`r")
val := StrReplace(val, "\t", "`t")

indx := 0
while indx := InStr(val, "\",, indx+1) {
if (SubStr(val, indx+1, 1) != "u") ? (pos -= StrLen(SubStr(val, indx)), next := "\") : 0
continue 2

xxxx := Abs("0x" . SubStr(val, indx+2, 4)) ; \uXXXX - JSON unicode escape sequence
if (xxxx < 0x100)
val := SubStr(val, 1, indx-1) . Chr(xxxx) . SubStr(val, indx+6)
}

if isKey {
key := val, next := ":"
continue
}

} else { ; number | true | false | null
val := SubStr(src, pos, indx := RegExMatch(src, "[\]\},\s]|$",, pos)-pos)

if IsInteger(val)
val += 0
else if IsFloat(val)
val += 0
else if (val == "true" || val == "false")
val := (val == "true")
else if (val == "null")
val := ""
else if isKey {
pos--, next := "#"
continue
}

pos += indx-1
}

isArrayType ? obj.Push(val) : obj[key] := val
next := obj == tree ? "" : isArrayType ? ",]" : ",}"
}
}

return _ConvertObjectMapsToObjects(tree[1])
}

JxonEncode(obj, indent:="", lvl:=1) {
if IsObject(obj) {
isArrayType := (obj is Array)
isMapType := (obj is Map)
isClassType := (obj is Object) && !isArrayType && !isMapType

if (!isArrayType && !isMapType && !isClassType) {
throw Error("Object type not supported.`r`n`r`nObj Type: " Type(obj), -1, Format("<Object at 0x{:p}>", ObjPtr(obj)))
}

if IsInteger(indent) {
if (indent < 0) {
throw Error("Indent parameter must be a postive integer.", -1, indent)
}
spaces := indent, indent := ""

loop spaces { ; ===> changed
indent .= " "
}
}
indt := ""

loop (indent ? lvl : 0) {
indt .= indent
}

lvl += 1, out := "" ; Make #Warn happy
itObj := isClassType ? obj.OwnProps() : obj

if (isClassType) {
out .= '"$type"' (indent ? ": " : ":") '"' ObjGetBase(obj).__Class '"' ( indent ? ",`n" . indt : "," )
}

for (k, v in itObj) {
if (IsObject(k) || k == "") {
throw Error("Invalid object key.", -1, k ? Format("<Object at 0x{:p}>", ObjPtr(obj)) : "<blank>")
}

if !isArrayType { ;// key ; ObjGetCapacity([k], 1)
out .= (ObjGetCapacity([k]) ? JxonEncode(k) : EscapeStr(k)) (indent ? ": " : ":") ; token + padding
}

out .= JxonEncode(v, indent, lvl) ; value
. ( indent ? ",`n" . indt : "," ) ; token + indent
}

if (out != "") {
out := Trim(out, ",`n" . indent)
if (indent != "") {
out := "`n" . indt . out . "`n" . SubStr(indt, StrLen(indent)+1)
}
}

return isArrayType ? "[" . out . "]" : "{" . out . "}"

} else {
if (Type(obj) != "String") {
return obj ; Number
} else {
return EscapeStr(obj)
}

EscapeStr(obj) {
obj := StrReplace(obj,"\","\\")
obj := StrReplace(obj,"`t","\t")
obj := StrReplace(obj,"`r","\r")
obj := StrReplace(obj,"`n","\n")
obj := StrReplace(obj,"`b","\b")
obj := StrReplace(obj,"`f","\f")
obj := StrReplace(obj,"/","\/")
obj := StrReplace(obj,'"','\"')

return '"' obj '"'
}
}
}

_ConvertObjectMapsToObjects(it) {
if !(it is Object) {
return it
}

if (it is Map && it.Has("$type")) {
obj := %it["$type"]%()
for k,v in it {
if (k == "$type") {
continue
}
obj.%k% := _ConvertObjectMapsToObjects(v)
}
return obj
}

for k,v in it {
it[k] := _ConvertObjectMapsToObjects(v)
}
return it
}

0 comments on commit c9cde51

Please sign in to comment.