Is there a simple and reliable way to process JSON data? Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Is there a simple and reliable way to process JSON data?

24 Jun 2019, 09:08

swagfag wrote: but it looks like urs doesnt handle the null case either
I changed my class a bit, now it handles:

Code: Select all

sJSON = {"key1": true, "key2": null, "key3": "test"}
jsObj := JSON.Parse(sJSON, true)
jsObj.key3 := "AHK"
MsgBox, % newJson := JSON.Stringify(jsObj, true)

class JSON
{
   static _ := JSON._GetJScripObject(), JS := JSON._.JS, script := JSON._.script
   
   Parse(JsonString, js := false)  {
      try oJSON := this.JS.("(" JsonString ")")
      catch  {
         MsgBox, Wrong JsonString!
         Return
      }
      Return js ? oJSON : this._CreateObject(oJSON)
   }
   
   Stringify(obj, js := false) {
      if js
         Return this.script.JSON.stringify(obj)
      else {
         sObj := this._ObjToString(obj)
         Return this.JS.("JSON.stringify(" . sObj . ")")
      }
   }
   
   _ObjToString(obj) {
      if IsObject( obj )  {
         isArray := true
         for key in obj {
            if IsObject(key)
               throw Exception("Invalid key")
            if !( key = A_Index || isArray := false )
               break
         }
         for k, v in obj
            str .= ( A_Index = 1 ? "" : "," ) . ( isArray ? "" : k . ":" ) . this._ObjToString(v)

         Return isArray ? "[" str "]" : "{" str "}"
      }
      else if !(obj*1 = "" || RegExMatch(obj, "\s"))
         Return obj
      else
         Return """" obj """"
   }

   _GetJScripObject()  {
      static doc
      doc := ComObjCreate("htmlfile")
      doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
      JS := ObjBindMethod(script := doc.parentWindow, "eval")
      JSON._AddMethods(JS)
      Return {JS: JS, script: script}
   }

   _AddMethods(ByRef JS)  {
      JScript =
      (
         Object.prototype.GetKeys = function () {
            var keys = []
            for (var k in this)
               if (this.hasOwnProperty(k))
                  keys.push(k)
            return keys
         }
         Object.prototype.IsArray = function () {
            var toStandardString = {}.toString
            return toStandardString.call(this) == '[object Array]'
         }
      )
      JS.(JScript)
   }

   _CreateObject(ObjJS)  {
      res := ObjJS.IsArray()
      if (res = "")
         Return ObjJS
      
      else if (res = -1)  {
         obj := []
         Loop % ObjJS.length
            obj[A_Index] := this._CreateObject(ObjJS[A_Index - 1])
      }
      else if (res = 0)  {
         obj := {}
         keys := ObjJS.GetKeys()
         Loop % keys.length
            k := keys[A_Index - 1], obj[k] := this._CreateObject(ObjJS[k])
      }
      Return obj
   }
}
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Is there a simple and reliable way to process JSON data?

24 Jun 2019, 09:33

Edited:

Code: Select all

sJSON = {"key1": true, "key2": null, "key3": "test"}
jsObj := JSON.Parse(sJSON, true)
jsObj.key3 := "AHK" ; must be unicode string
MsgBox, % newJson := JSON.Stringify(jsObj, true)

ahkObj := JSON.Parse(sJSON)
ahkObj.key3 := "Hello"
MsgBox, % newJson := JSON.Stringify(ahkObj)

class JSON
{
   static JS := JSON._GetJScripObject()
   
   Parse(JsonString, js := false)  {
      try oJSON := this.JS.eval("(" JsonString ")")
      catch  {
         MsgBox, Wrong JsonString!
         Return
      }
      Return js ? oJSON : this._CreateObject(oJSON)
   }
   
   Stringify(obj, js := false) {
      if js
         Return this.JS.JSON.stringify(obj)
      else {
         sObj := this._ObjToString(obj)
         Return this.JS.eval("JSON.stringify(" . sObj . ")")
      }
   }
   
   _ObjToString(obj) {
      if IsObject( obj )  {
         isArray := true
         for key in obj {
            if IsObject(key)
               throw Exception("Invalid key")
            if !( key = A_Index || isArray := false )
               break
         }
         for k, v in obj
            str .= ( A_Index = 1 ? "" : "," ) . ( isArray ? "" : k . ":" ) . this._ObjToString(v)

         Return isArray ? "[" str "]" : "{" str "}"
      }
      else if !(obj*1 = "" || RegExMatch(obj, "\s"))
         Return obj
      else
         Return """" obj """"
   }

   _GetJScripObject()  {
      static doc
      doc := ComObjCreate("htmlfile")
      doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
      JS := doc.parentWindow
      JSON._AddMethods(JS)
      Return JS
   }

   _AddMethods(ByRef JS)  {
      JScript =
      (
         Object.prototype.GetKeys = function () {
            var keys = []
            for (var k in this)
               if (this.hasOwnProperty(k))
                  keys.push(k)
            return keys
         }
         Object.prototype.IsArray = function () {
            var toStandardString = {}.toString
            return toStandardString.call(this) == '[object Array]'
         }
      )
      JS.eval(JScript)
   }

   _CreateObject(ObjJS)  {
      res := ObjJS.IsArray()
      if (res = "")
         Return ObjJS
      
      else if (res = -1)  {
         obj := []
         Loop % ObjJS.length
            obj[A_Index] := this._CreateObject(ObjJS[A_Index - 1])
      }
      else if (res = 0)  {
         obj := {}
         keys := ObjJS.GetKeys()
         Loop % keys.length
            k := keys[A_Index - 1], obj[k] := this._CreateObject(ObjJS[k])
      }
      Return obj
   }
}
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Is there a simple and reliable way to process JSON data?

24 Jun 2019, 10:32

Code: Select all

;                  v----------+
sJSON = {"key1": true} ;      |
ahkObj := JSON.Parse(sJSON) ; |
MsgBox % ahkObj.key1 ; -1 <---+
-1 is truthy in ahk and most people probably dont do math with booleans, but thats wrong
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Is there a simple and reliable way to process JSON data?

24 Jun 2019, 10:51

I added GetKey() method:

Code: Select all

sJSON = {"key1": true}
MsgBox, % JSON.GetKey(sJSON, "key1")

class JSON
{
   static JS := JSON._GetJScripObject()
   
   Parse(JsonString, js := false)  {
      try oJSON := this.JS.eval("(" JsonString ")")
      catch  {
         MsgBox, Wrong JsonString!
         Return
      }
      Return js ? oJSON : this._CreateObject(oJSON)
   }
   
   Stringify(obj, js := false) {
      if js
         Return this.JS.JSON.stringify(obj)
      else {
         sObj := this._ObjToString(obj)
         Return this.JS.eval("JSON.stringify(" . sObj . ")")
      }
   }
   
   GetKey(sJson, key) {
      Return this.JS.eval("JSON.stringify((" . sJson . ")." . key . ")")
   }
   
   _ObjToString(obj) {
      if IsObject( obj )  {
         isArray := true
         for key in obj {
            if IsObject(key)
               throw Exception("Invalid key")
            if !( key = A_Index || isArray := false )
               break
         }
         for k, v in obj
            str .= ( A_Index = 1 ? "" : "," ) . ( isArray ? "" : k . ":" ) . this._ObjToString(v)

         Return isArray ? "[" str "]" : "{" str "}"
      }
      else if !(obj*1 = "" || RegExMatch(obj, "\s"))
         Return obj
      else
         Return """" obj """"
   }

   _GetJScripObject()  {
      static doc
      doc := ComObjCreate("htmlfile")
      doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
      JS := doc.parentWindow
      JSON._AddMethods(JS)
      Return JS
   }

   _AddMethods(ByRef JS)  {
      JScript =
      (
         Object.prototype.GetKeys = function () {
            var keys = []
            for (var k in this)
               if (this.hasOwnProperty(k))
                  keys.push(k)
            return keys
         }
         Object.prototype.IsArray = function () {
            var toStandardString = {}.toString
            return toStandardString.call(this) == '[object Array]'
         }
      )
      JS.eval(JScript)
   }

   _CreateObject(ObjJS)  {
      res := ObjJS.IsArray()
      if (res = "")
         Return ObjJS
      
      else if (res = -1)  {
         obj := []
         Loop % ObjJS.length
            obj[A_Index] := this._CreateObject(ObjJS[A_Index - 1])
      }
      else if (res = 0)  {
         obj := {}
         keys := ObjJS.GetKeys()
         Loop % keys.length
            k := keys[A_Index - 1], obj[k] := this._CreateObject(ObjJS[k])
      }
      Return obj
   }
}
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Is there a simple and reliable way to process JSON data?

24 Jun 2019, 11:36

Also added SetKey() method:

Code: Select all

sJSON = {"key1": true}
MsgBox, % JSON.GetKey(sJSON, "key1")

MsgBox, % newJson := JSON.SetKey(sJSON, "key2", "false")
MsgBox, % newJson := JSON.SetKey(newJson, "key3", "'test'")
MsgBox, % newJson := JSON.SetKey(newJson, "key4", "{'key1':'value', 'key2':['one', null]}")

MsgBox, % JSON.GetKey(newJson, "key4.key2[0]")
MsgBox, % JSON.GetKey(newJson, "key4.key2[1]")

class JSON
{
   static JS := JSON._GetJScripObject()
   
   Parse(sJson, js := false)  {
      try oJSON := this.JS.eval("(" sJson ")")
      catch  {
         MsgBox, Wrong JSON string!
         Return
      }
      Return js ? oJSON : this._CreateObject(oJSON)
   }
   
   Stringify(obj, js := false) {
      if js
         Return this.JS.JSON.stringify(obj)
      else {
         sObj := this._ObjToString(obj)
         Return this.JS.eval("JSON.stringify(" . sObj . ")")
      }
   }
   
   GetKey(sJson, key) {
      Return this.JS.eval("JSON.stringify((" . sJson . ")." . key . ")")
   }
   
   SetKey(sJson, key, value) {
      res := this.JS.eval("var obj = (" . sJson . "); obj." . key . "=" . value . "; JSON.stringify(obj)")
      this.JS.eval("obj = ''")
      Return res
   }
   
   _ObjToString(obj) {
      if IsObject( obj )  {
         isArray := true
         for key in obj {
            if IsObject(key)
               throw Exception("Invalid key")
            if !( key = A_Index || isArray := false )
               break
         }
         for k, v in obj
            str .= ( A_Index = 1 ? "" : "," ) . ( isArray ? "" : k . ":" ) . this._ObjToString(v)

         Return isArray ? "[" str "]" : "{" str "}"
      }
      else if !(obj*1 = "" || RegExMatch(obj, "\s"))
         Return obj
      else
         Return """" obj """"
   }

   _GetJScripObject()  {
      static doc
      doc := ComObjCreate("htmlfile")
      doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
      JS := doc.parentWindow
      JSON._AddMethods(JS)
      Return JS
   }

   _AddMethods(ByRef JS)  {
      JScript =
      (
         Object.prototype.GetKeys = function () {
            var keys = []
            for (var k in this)
               if (this.hasOwnProperty(k))
                  keys.push(k)
            return keys
         }
         Object.prototype.IsArray = function () {
            var toStandardString = {}.toString
            return toStandardString.call(this) == '[object Array]'
         }
      )
      JS.eval(JScript)
   }

   _CreateObject(ObjJS)  {
      res := ObjJS.IsArray()
      if (res = "")
         Return ObjJS
      
      else if (res = -1)  {
         obj := []
         Loop % ObjJS.length
            obj[A_Index] := this._CreateObject(ObjJS[A_Index - 1])
      }
      else if (res = 0)  {
         obj := {}
         keys := ObjJS.GetKeys()
         Loop % keys.length
            k := keys[A_Index - 1], obj[k] := this._CreateObject(ObjJS[k])
      }
      Return obj
   }
}
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Is there a simple and reliable way to process JSON data?

24 Jun 2019, 12:10

great, so it works correctly now, although we've truly gone back to square 1
the pain in the ass is that u have to juggle strings back and forth every time u want to do anything useful
:roll:
at least ur lib allows one to choose
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Is there a simple and reliable way to process JSON data?

24 Jun 2019, 12:35

Just try do the same with any another library:

Code: Select all

sJSON = {}

MsgBox, % newJson := JSON.SetKey(sJSON, "key1", "'✓'")
MsgBox, % newJson := JSON.SetKey(newJson, "key2", "null")
MsgBox, % JSON.GetKey(newJson, "key2")

class JSON
{
   static JS := JSON._GetJScripObject()
   
   Parse(sJson, js := false)  {
      if jsObj := this.VerifyJson(sJson)
         Return js ? jsObj : this._CreateObject(jsObj)
   }
   
   Stringify(obj, js := false) {
      if js
         Return this.JS.JSON.stringify(obj)
      else {
         sObj := this._ObjToString(obj)
         Return this.JS.eval("JSON.stringify(" . sObj . ")")
      }
   }
   
   GetKey(sJson, key) {
      if !this.VerifyJson(sJson)
         Return
      try res := this.JS.eval("JSON.stringify((" . sJson . ")." . key . ")")
      catch {
         MsgBox, Wrong key:`n`n%key%
         Return
      }
      Return res
   }
   
   SetKey(sJson, key, value) {
      if !(this.VerifyJson(sJson) && this.VerifyJson(value))
         Return
      res := this.JS.eval("var obj = (" . sJson . "); obj." . key . "=" . value . "; JSON.stringify(obj)")
      this.JS.eval("obj = ''")
      Return res
   }
   
   VerifyJson(sJson) {
      try jsObj := this.JS.eval("(" sJson ")")
      catch {
         MsgBox, Wrong JSON string:`n`n%sJson%
         Return
      }
      Return IsObject(jsObj) ? jsObj : true
   }
   
   _ObjToString(obj) {
      if IsObject( obj )  {
         isArray := true
         for key in obj {
            if IsObject(key)
               throw Exception("Invalid key")
            if !( key = A_Index || isArray := false )
               break
         }
         for k, v in obj
            str .= ( A_Index = 1 ? "" : "," ) . ( isArray ? "" : k . ":" ) . this._ObjToString(v)

         Return isArray ? "[" str "]" : "{" str "}"
      }
      else if !(obj*1 = "" || RegExMatch(obj, "\s"))
         Return obj
      else
         Return """" obj """"
   }

   _GetJScripObject()  {
      static doc
      doc := ComObjCreate("htmlfile")
      doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
      JS := doc.parentWindow
      JSON._AddMethods(JS)
      Return JS
   }

   _AddMethods(ByRef JS)  {
      JScript =
      (
         Object.prototype.GetKeys = function () {
            var keys = []
            for (var k in this)
               if (this.hasOwnProperty(k))
                  keys.push(k)
            return keys
         }
         Object.prototype.IsArray = function () {
            var toStandardString = {}.toString
            return toStandardString.call(this) == '[object Array]'
         }
      )
      JS.eval(JScript)
   }

   _CreateObject(ObjJS)  {
      res := ObjJS.IsArray()
      if (res = "")
         Return ObjJS
      
      else if (res = -1)  {
         obj := []
         Loop % ObjJS.length
            obj[A_Index] := this._CreateObject(ObjJS[A_Index - 1])
      }
      else if (res = 0)  {
         obj := {}
         keys := ObjJS.GetKeys()
         Loop % keys.length
            k := keys[A_Index - 1], obj[k] := this._CreateObject(ObjJS[k])
      }
      Return obj
   }
}
Do you still worry about the pain in the ass? ;)
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Is there a simple and reliable way to process JSON data?

24 Jun 2019, 13:44

if im gonna have to copy paste .GetKey() a thousand times cuz i cant use foreach, then yes, very much so
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Is there a simple and reliable way to process JSON data?

24 Jun 2019, 14:05

If you need a specific key, use GetKey(), but you still can use JSON.Parse(sJson) to get an ahk-object. Or I don't understand what you mean.
Anyway you can using a library you like more.
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Is there a simple and reliable way to process JSON data?

24 Jun 2019, 15:27

the problem is that both interfaces are separate from each other. u can either start processing a json string as an AHK object(in which case, true becomes -1, null gets mangled, and possibly other weird things, etc) OR u can start processing the json string in the context of the JS comobject(in which case, u cant use any of the ahk stuff on it, no foreach, no object methods, no fast access. u could process it with pure js(if u knew how), but ull have to ferry data in and out via strings)

however, at NO point can u ever switch between those 2 interfaces. hence, u can only choose between simple/fast/wrong and correct/slow/pita
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Is there a simple and reliable way to process JSON data?

24 Jun 2019, 19:07

It all depends on the ultimate goal you want to achieve. If you have JSON and want to get an ahk-object it makes no sense talk about true, null and so on, since AHK does not support them. But if you need some keys or the part of this JSON, GetKey() will be useful.
swagfag wrote: no foreach, no object methods, no fast access
Fast access is GetKey(), isn't it?
Added some improvements, RemoveKey() and Enum():

Code: Select all

sJson = ["value to delete", null, false, {"key1": 5, "key2": true, "key3": "string"}]

for k, v in JSON.Enum(sJson)
   MsgBox, % "key: " k "`nvalue: " v

for k, v in JSON.Enum(sJson)
   for k, v in JSON.Enum(v)
      MsgBox, % "key: " k "`nvalue: " v

MsgBox, % JSON.RemoveKey(sJson, "[0]")

sJson = [null, false, {"key1": 5, "key2": true, "key3": "string"}, {"key1": 0, "key2": false, "key3": "AHK"}]

for k, v in JSON.Enum(sJson)
   MsgBox, % "key: " k "`nvalue: " v

for k, v in JSON.Enum(sJson)
   for k, v in JSON.Enum(v)
      MsgBox, % "key: " k "`nvalue: " v
   
for k, v in JSON.Enum(sJson, "[3]")
   MsgBox, % "key: " k "`nvalue: " v

class JSON
{
   static JS := JSON._GetJScriptObject()
   
   Parse(sJson, js := false)  {
      if jsObj := this.VerifyJson(sJson)
         Return js ? jsObj : this._CreateObject(jsObj)
   }
   
   Stringify(obj, js := false) {
      if js
         Return this.JS.JSON.stringify(obj)
      else {
         sObj := this._ObjToString(obj)
         Return this.JS.eval("JSON.stringify(" . sObj . ")")
      }
   }
   
   GetKey(sJson, key) {
      if !this.VerifyJson(sJson)
         Return
      
      try res := this.JS.eval("JSON.stringify((" . sJson . ")" . (SubStr(key, 1, 1) = "[" ? "" : ".") . key . ")")
      catch {
         MsgBox, Wrong key:`n`n%key%
         Return
      }
      Return res
   }
   
   SetKey(sJson, key, value) {
      if !(this.VerifyJson(sJson) && this.VerifyJson(value))
         Return
      
      res := this.JS.eval( "var obj = (" . sJson . ");"
                         . "obj" . (SubStr(key, 1, 1) = "[" ? "" : ".") . key . "=" . value . ";"
                         . "JSON.stringify(obj)" )
      this.JS.eval("obj = ''")
      Return res
   }
   
   RemoveKey(sJson, key) {
      if !this.VerifyJson(sJson)
         Return
      
      sign := SubStr(key, 1, 1) = "[" ? "" : "."
      if !RegExMatch(key, "(.*)\[(\d+)]$", match)
         res := this.JS.eval("var obj = (" . sJson . "); delete obj" . sign . key . "; JSON.stringify(obj)")
      else
         res := this.JS.eval( "var obj = (" . sJson . ");" 
                            . "obj" . (match1 != "" ? sign . match1 : "") . ".splice(" . match2 . ", 1);"
                            . "JSON.stringify(obj)" )
      this.JS.eval("obj = ''")
      Return res
   }
   
   Enum(sJson, key := "") {
      if !this.VerifyJson(sJson)
         Return
      
      conc := key ? (SubStr(key, 1, 1) = "[" ? "" : ".") . key : ""
      try jsObj := this.JS.eval("(" sJson ")" . conc)
      catch {
         MsgBox, Wrong key:`n`n%key%
         Return
      }
      res := jsObj.IsArray()
      if (res = "")
         Return
      obj := {}
      if (res = -1)  {
         Loop % jsObj.length
            obj[A_Index - 1] := this.JS.eval("JSON.stringify((" sJson ")" . conc . "[" . (A_Index - 1) . "])")
      }
      else if (res = 0) {
         keys := jsObj.GetKeys()
         Loop % keys.length
            k := keys[A_Index - 1], obj[k] := this.JS.eval("JSON.stringify((" sJson ")" . conc . "." . k . ")")
      }
      Return obj
   }
   
   VerifyJson(sJson) {
      try jsObj := this.JS.eval("(" sJson ")")
      catch {
         MsgBox, Wrong JSON string:`n`n%sJson%
         Return
      }
      Return IsObject(jsObj) ? jsObj : true
   }
   
   _ObjToString(obj) {
      if IsObject( obj )  {
         isArray := true
         for key in obj {
            if IsObject(key)
               throw Exception("Invalid key")
            if !( key = A_Index || isArray := false )
               break
         }
         for k, v in obj
            str .= ( A_Index = 1 ? "" : "," ) . ( isArray ? "" : k . ":" ) . this._ObjToString(v)

         Return isArray ? "[" str "]" : "{" str "}"
      }
      else if !(obj*1 = "" || RegExMatch(obj, "\s"))
         Return obj
      else
         Return """" obj """"
   }

   _GetJScriptObject()  {
      static doc
      doc := ComObjCreate("htmlfile")
      doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
      JS := doc.parentWindow
      JSON._AddMethods(JS)
      Return JS
   }

   _AddMethods(ByRef JS)  {
      JScript =
      (
         Object.prototype.GetKeys = function () {
            var keys = []
            for (var k in this)
               if (this.hasOwnProperty(k))
                  keys.push(k)
            return keys
         }
         Object.prototype.IsArray = function () {
            var toStandardString = {}.toString
            return toStandardString.call(this) == '[object Array]'
         }
      )
      JS.eval(JScript)
   }

   _CreateObject(jsObj)  {
      res := jsObj.IsArray()
      if (res = "")
         Return jsObj
      
      else if (res = -1)  {
         obj := []
         Loop % jsObj.length
            obj[A_Index] := this._CreateObject(jsObj[A_Index - 1])
      }
      else if (res = 0)  {
         obj := {}
         keys := jsObj.GetKeys()
         Loop % keys.length
            k := keys[A_Index - 1], obj[k] := this._CreateObject(jsObj[k])
      }
      Return obj
   }
}
But I'm sure, you will continue using coco library, since it is devoid of the above disadvantages. :)
Last edited by teadrinker on 24 Jun 2019, 20:44, edited 3 times in total.
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Is there a simple and reliable way to process JSON data?

24 Jun 2019, 20:00

swagfag wrote: correct/slow/pita
By the way, are you sure mine is slower? Did you compare them?
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Is there a simple and reliable way to process JSON data?

24 Jun 2019, 21:23

no, i just assumed. lo and behold my assumption was correct, turns out. strings are slow, yes?

Code: Select all

#NoEnv
#SingleInstance Force
#Warn ClassOverwrite
SetBatchLines -1

DllCall("QueryPerformanceFrequency", "Int64*", frequency)
Timings := {}

whr := ComObjCreate("WinHttp.WinHttpRequest.5.1")
whr.Open("GET", "https://gist.githubusercontent.com/sswwaagg/4533e94fa6acd68503f957d60bfb920b/raw/5f7298d0c5ec2221ba82c246e60f08c49a9ee011/TestFile.json") ; https://pastebin.com/raw/4xRrs5Kn
whr.Send()
jsonString := whr.ResponseText

DllCall("QueryPerformanceCounter", "Int64*", start)
CocoAhkObj := CocoJson.Load(jsonString)
DllCall("QueryPerformanceCounter", "Int64*", stop)
ticks := stop - start
elapsed := ticks * 1000
elapsed /= frequency
Timings.coco_ahk_parse := {ms: elapsed, ticks: ticks}

DllCall("QueryPerformanceCounter", "Int64*", start)
TeaAhkObj := JSON.Parse(jsonString)
DllCall("QueryPerformanceCounter", "Int64*", stop)
ticks := stop - start
elapsed := ticks * 1000
elapsed /= frequency
Timings.tea_ahk_parse := {ms: elapsed, ticks: ticks}

DllCall("QueryPerformanceCounter", "Int64*", start)
TeaJsObj := JSON.Parse(jsonString, true)
DllCall("QueryPerformanceCounter", "Int64*", stop)
ticks := stop - start
elapsed := ticks * 1000
elapsed /= frequency
Timings.tea_js_parse := {ms: elapsed, ticks: ticks}

FriendNames := []
FriendNames.SetCapacity(1000000)
DllCall("QueryPerformanceCounter", "Int64*", start)
for each, Entry in CocoAhkObj
	for each, Friend in Entry.friends
		FriendNames.Push(Friend.name)		
DllCall("QueryPerformanceCounter", "Int64*", stop)
ticks := stop - start
elapsed := ticks * 1000
elapsed /= frequency
Timings.coco_ahk_getfriends := {ms: elapsed, ticks: ticks}

FriendNames := []
FriendNames.SetCapacity(1000000)
DllCall("QueryPerformanceCounter", "Int64*", start)
for each, Entry in TeaAhkObj
	for each, Friend in Entry.friends
		FriendNames.Push(Friend.name)		
DllCall("QueryPerformanceCounter", "Int64*", stop)
ticks := stop - start
elapsed := ticks * 1000
elapsed /= frequency
Timings.tea_ahk_getfriends := {ms: elapsed, ticks: ticks}

FriendNames := []
FriendNames.SetCapacity(1000000)
DllCall("QueryPerformanceCounter", "Int64*", start)
Loop 50
{
	i := A_Index - 1

	Entry := TeaJsObj[i]
	Friends := Entry.friends

	Loop 3
	{
		j := A_Index - 1

		Friend := Friends[j]

		FriendNames.Push(Friend.name)
	}
}
DllCall("QueryPerformanceCounter", "Int64*", stop)
ticks := stop - start
elapsed := ticks * 1000
elapsed /= frequency
Timings.tea_js_getfriends := {ms: elapsed, ticks: ticks}

FriendNames := []
FriendNames.SetCapacity(1000000)
DllCall("QueryPerformanceCounter", "Int64*", start)
Loop 50
{
	i := A_Index - 1

	Loop 3
	{
		j := A_Index - 1

		FriendNames.Push(JSON.GetKey(jsonString, Trim("[" i "].friends[" j "].name", """")))
	}
}
DllCall("QueryPerformanceCounter", "Int64*", stop)
ticks := stop - start
elapsed := ticks * 1000
elapsed /= frequency
Timings.tea_json_getfriends := {ms: elapsed, ticks: ticks}

FriendNames := []
FriendNames.SetCapacity(1000000)
DllCall("QueryPerformanceCounter", "Int64*", start)
for each, Entry in JSON.Enum(jsonString)
	for each, Friend in JSON.Enum(JSON.GetKey(Entry, "friends"))
		FriendNames.Push(Trim(JSON.GetKey(Friend, "name"), """"))	
DllCall("QueryPerformanceCounter", "Int64*", stop)
ticks := stop - start
elapsed := ticks * 1000
elapsed /= frequency
Timings.tea_enum_getfriends := {ms: elapsed, ticks: ticks}

for event, Time in Timings
	results .= event ":`t" Time.ticks "(" Time.ms " ms)`n"

MsgBox % Clipboard := results

/*
coco_ahk_parse:       207210    ( 20 ms)
tea_ahk_parse:         99084    (  9 ms)
tea_js_parse:           5498    (  0 ms)
coco_ahk_getfriends:    1214    (  0 ms)
tea_ahk_getfriends:      862    (  0 ms)
tea_js_getfriends:      4135    (  0 ms)
tea_json_getfriends: 3189953    (318 ms)
tea_enum_getfriends: 1012029    (101 ms)
*/

class JSON
{
   static JS := JSON._GetJScriptObject()
   
   Parse(sJson, js := false)  {
      if jsObj := this.VerifyJson(sJson)
         Return js ? jsObj : this._CreateObject(jsObj)
   }
   
   Stringify(obj, js := false) {
      if js
         Return this.JS.JSON.stringify(obj)
      else {
         sObj := this._ObjToString(obj)
         Return this.JS.eval("JSON.stringify(" . sObj . ")")
      }
   }
   
   GetKey(sJson, key) {
      if !this.VerifyJson(sJson)
         Return
      try res := this.JS.eval("JSON.stringify((" . sJson . ")" . (SubStr(key, 1, 1) = "[" ? "" : ".") . key . ")")
      catch {
         MsgBox, Wrong key:`n`n%key%
         Return
      }
      Return res
   }
   
   Enum(sJson, key := "") {
      if !this.VerifyJson(sJson)
         Return
      
      sign := key ? (SubStr(key, 1, 1) = "[" ? "" : ".") . key : ""
      try jsObj := this.JS.eval("(" sJson ")" . sign)
      catch {
         MsgBox, Wrong key:`n`n%key%
         Return
      }
      res := jsObj.IsArray()
      if (res = "")
         Return
      obj := {}
      if (res = -1)  {
         Loop % jsObj.length
            obj[A_Index - 1] := this.JS.eval("JSON.stringify((" sJson ")" . sign . "[" . (A_Index - 1) . "])")
      }
      else if (res = 0) {
         keys := jsObj.GetKeys()
         Loop % keys.length
            k := keys[A_Index - 1], obj[k] := this.JS.eval("JSON.stringify((" sJson ")" . sign . "." . k . ")")
      }
      Return obj
   }
   
   SetKey(sJson, key, value) {
      if !(this.VerifyJson(sJson) && this.VerifyJson(value))
         Return
      res := this.JS.eval( "var obj = (" . sJson . ");"
                         . "obj" . (SubStr(key, 1, 1) = "[" ? "" : ".") . key . "=" . value . ";"
                         . "JSON.stringify(obj)" )
      this.JS.eval("obj = ''")
      Return res
   }
   
   RemoveKey(sJson, key) {
      if !this.VerifyJson(sJson)
         Return
      
      sign := key ? (SubStr(key, 1, 1) = "[" ? "" : ".") . key : ""
      if RegExMatch(key, "(.*)\[(\d+)]$", match)
         res := this.JS.eval( "var obj = (" . sJson . ");" 
                            . "obj" . (match1 != "" ? sign . match1 : "") . ".splice(" . match2 . ", 1); JSON.stringify(obj)" )
      else
         res := this.JS.eval("var obj = (" . sJson . "); delete obj." . key . "; JSON.stringify(obj)")
      this.JS.eval("obj = ''")
      Return res
   }
   
   VerifyJson(sJson) {
      try jsObj := this.JS.eval("(" sJson ")")
      catch {
         MsgBox, Wrong JSON string:`n`n%sJson%
         Return
      }
      Return IsObject(jsObj) ? jsObj : true
   }
   
   _ObjToString(obj) {
      if IsObject( obj )  {
         isArray := true
         for key in obj {
            if IsObject(key)
               throw Exception("Invalid key")
            if !( key = A_Index || isArray := false )
               break
         }
         for k, v in obj
            str .= ( A_Index = 1 ? "" : "," ) . ( isArray ? "" : k . ":" ) . this._ObjToString(v)

         Return isArray ? "[" str "]" : "{" str "}"
      }
      else if !(obj*1 = "" || RegExMatch(obj, "\s"))
         Return obj
      else
         Return """" obj """"
   }

   _GetJScriptObject()  {
      static doc
      doc := ComObjCreate("htmlfile")
      doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
      JS := doc.parentWindow
      JSON._AddMethods(JS)
      Return JS
   }

   _AddMethods(ByRef JS)  {
      JScript =
      (
         Object.prototype.GetKeys = function () {
            var keys = []
            for (var k in this)
               if (this.hasOwnProperty(k))
                  keys.push(k)
            return keys
         }
         Object.prototype.IsArray = function () {
            var toStandardString = {}.toString
            return toStandardString.call(this) == '[object Array]'
         }
      )
      JS.eval(JScript)
   }

   _CreateObject(ObjJS)  {
      res := ObjJS.IsArray()
      if (res = "")
         Return ObjJS
      
      else if (res = -1)  {
         obj := []
         Loop % ObjJS.length
            obj[A_Index] := this._CreateObject(ObjJS[A_Index - 1])
      }
      else if (res = 0)  {
         obj := {}
         keys := ObjJS.GetKeys()
         Loop % keys.length
            k := keys[A_Index - 1], obj[k] := this._CreateObject(ObjJS[k])
      }
      Return obj
   }
}


/**
 * Lib: JSON.ahk
 *     JSON lib for AutoHotkey.
 * Version:
 *     v2.1.3 [updated 04/18/2016 (MM/DD/YYYY)]
 * License:
 *     WTFPL [http://wtfpl.net/]
 * Requirements:
 *     Latest version of AutoHotkey (v1.1+ or v2.0-a+)
 * Installation:
 *     Use #Include JSON.ahk or copy into a function library folder and then
 *     use #Include <JSON>
 * Links:
 *     GitHub:     - https://github.com/cocobelgica/AutoHotkey-JSON
 *     Forum Topic - http://goo.gl/r0zI8t
 *     Email:      - cocobelgica <at> gmail <dot> com
 */


/**
 * Class: JSON
 *     The JSON object contains methods for parsing JSON and converting values
 *     to JSON. Callable - NO; Instantiable - YES; Subclassable - YES;
 *     Nestable(via #Include) - NO.
 * Methods:
 *     Load() - see relevant documentation before method definition header
 *     Dump() - see relevant documentation before method definition header
 */
class CocoJson
{
	/**
	 * Method: Load
	 *     Parses a JSON string into an AHK value
	 * Syntax:
	 *     value := JSON.Load( text [, reviver ] )
	 * Parameter(s):
	 *     value      [retval] - parsed value
	 *     text    [in, ByRef] - JSON formatted string
	 *     reviver   [in, opt] - function object, similar to JavaScript's
	 *                           JSON.parse() 'reviver' parameter
	 */
	class Load extends CocoJson.Functor
	{
		Call(self, ByRef text, reviver:="")
		{
			this.rev := IsObject(reviver) ? reviver : false
		; Object keys(and array indices) are temporarily stored in arrays so that
		; we can enumerate them in the order they appear in the document/text instead
		; of alphabetically. Skip if no reviver function is specified.
			this.keys := this.rev ? {} : false

			static quot := Chr(34), bashq := "\" . quot
			     , json_value := quot . "{[01234567890-tfn"
			     , json_value_or_array_closing := quot . "{[]01234567890-tfn"
			     , object_key_or_object_closing := quot . "}"

			key := ""
			is_key := false
			root := {}
			stack := [root]
			next := json_value
			pos := 0

			while ((ch := SubStr(text, ++pos, 1)) != "") {
				if InStr(" `t`r`n", ch)
					continue
				if !InStr(next, ch, 1)
					this.ParseError(next, text, pos)

				holder := stack[1]
				is_array := holder.IsArray

				if InStr(",:", ch) {
					next := (is_key := !is_array && ch == ",") ? quot : json_value

				} else if InStr("}]", ch) {
					ObjRemoveAt(stack, 1)
					next := stack[1]==root ? "" : stack[1].IsArray ? ",]" : ",}"

				} else {
					if InStr("{[", ch) {
					; Check if Array() is overridden and if its return value has
					; the 'IsArray' property. If so, Array() will be called normally,
					; otherwise, use a custom base object for arrays
						static json_array := Func("Array").IsBuiltIn || ![].IsArray ? {IsArray: true} : 0
					
					; sacrifice readability for minor(actually negligible) performance gain
						(ch == "{")
							? ( is_key := true
							  , value := {}
							  , next := object_key_or_object_closing )
						; ch == "["
							: ( value := json_array ? new json_array : []
							  , next := json_value_or_array_closing )
						
						ObjInsertAt(stack, 1, value)

						if (this.keys)
							this.keys[value] := []
					
					} else {
						if (ch == quot) {
							i := pos
							while (i := InStr(text, quot,, i+1)) {
								value := StrReplace(SubStr(text, pos+1, i-pos-1), "\\", "\u005c")

								static tail := A_AhkVersion<"2" ? 0 : -1
								if (SubStr(value, tail) != "\")
									break
							}

							if (!i)
								this.ParseError("'", text, pos)

							  value := StrReplace(value,  "\/",  "/")
							, value := StrReplace(value, bashq, quot)
							, value := StrReplace(value,  "\b", "`b")
							, value := StrReplace(value,  "\f", "`f")
							, value := StrReplace(value,  "\n", "`n")
							, value := StrReplace(value,  "\r", "`r")
							, value := StrReplace(value,  "\t", "`t")

							pos := i ; update pos
							
							i := 0
							while (i := InStr(value, "\",, i+1)) {
								if !(SubStr(value, i+1, 1) == "u")
									this.ParseError("\", text, pos - StrLen(SubStr(value, i+1)))

								uffff := Abs("0x" . SubStr(value, i+2, 4))
								if (A_IsUnicode || uffff < 0x100)
									value := SubStr(value, 1, i-1) . Chr(uffff) . SubStr(value, i+6)
							}

							if (is_key) {
								key := value, next := ":"
								continue
							}
						
						} else {
							value := SubStr(text, pos, i := RegExMatch(text, "[\]\},\s]|$",, pos)-pos)

							static number := "number", integer :="integer"
							if value is %number%
							{
								if value is %integer%
									value += 0
							}
							else if (value == "true" || value == "false")
								value := %value% + 0
							else if (value == "null")
								value := ""
							else
							; we can do more here to pinpoint the actual culprit
							; but that's just too much extra work.
								this.ParseError(next, text, pos, i)

							pos += i-1
						}

						next := holder==root ? "" : is_array ? ",]" : ",}"
					} ; If InStr("{[", ch) { ... } else

					is_array? key := ObjPush(holder, value) : holder[key] := value

					if (this.keys && this.keys.HasKey(holder))
						this.keys[holder].Push(key)
				}
			
			} ; while ( ... )

			return this.rev ? this.Walk(root, "") : root[""]
		}

		ParseError(expect, ByRef text, pos, len:=1)
		{
			static quot := Chr(34), qurly := quot . "}"
			
			line := StrSplit(SubStr(text, 1, pos), "`n", "`r").Length()
			col := pos - InStr(text, "`n",, -(StrLen(text)-pos+1))
			msg := Format("{1}`n`nLine:`t{2}`nCol:`t{3}`nChar:`t{4}"
			,     (expect == "")     ? "Extra data"
			    : (expect == "'")    ? "Unterminated string starting at"
			    : (expect == "\")    ? "Invalid \escape"
			    : (expect == ":")    ? "Expecting ':' delimiter"
			    : (expect == quot)   ? "Expecting object key enclosed in double quotes"
			    : (expect == qurly)  ? "Expecting object key enclosed in double quotes or object closing '}'"
			    : (expect == ",}")   ? "Expecting ',' delimiter or object closing '}'"
			    : (expect == ",]")   ? "Expecting ',' delimiter or array closing ']'"
			    : InStr(expect, "]") ? "Expecting JSON value or array closing ']'"
			    :                      "Expecting JSON value(string, number, true, false, null, object or array)"
			, line, col, pos)

			static offset := A_AhkVersion<"2" ? -3 : -4
			throw Exception(msg, offset, SubStr(text, pos, len))
		}

		Walk(holder, key)
		{
			value := holder[key]
			if IsObject(value) {
				for i, k in this.keys[value] {
					; check if ObjHasKey(value, k) ??
					v := this.Walk(value, k)
					if (v != CocoJson.Undefined)
						value[k] := v
					else
						ObjDelete(value, k)
				}
			}
			
			return this.rev.Call(holder, key, value)
		}
	}

	/**
	 * Method: Dump
	 *     Converts an AHK value into a JSON string
	 * Syntax:
	 *     str := CocoJson.Dump( value [, replacer, space ] )
	 * Parameter(s):
	 *     str        [retval] - JSON representation of an AHK value
	 *     value          [in] - any value(object, string, number)
	 *     replacer  [in, opt] - function object, similar to JavaScript's
	 *                           CocoJson.stringify() 'replacer' parameter
	 *     space     [in, opt] - similar to JavaScript's CocoJson.stringify()
	 *                           'space' parameter
	 */
	class Dump extends CocoJson.Functor
	{
		Call(self, value, replacer:="", space:="")
		{
			this.rep := IsObject(replacer) ? replacer : ""

			this.gap := ""
			if (space) {
				static integer := "integer"
				if space is %integer%
					Loop, % ((n := Abs(space))>10 ? 10 : n)
						this.gap .= " "
				else
					this.gap := SubStr(space, 1, 10)

				this.indent := "`n"
			}

			return this.Str({"": value}, "")
		}

		Str(holder, key)
		{
			value := holder[key]

			if (this.rep)
				value := this.rep.Call(holder, key, ObjHasKey(holder, key) ? value : CocoJson.Undefined)

			if IsObject(value) {
			; Check object type, skip serialization for other object types such as
			; ComObject, Func, BoundFunc, FileObject, RegExMatchObject, Property, etc.
				static type := A_AhkVersion<"2" ? "" : Func("Type")
				if (type ? type.Call(value) == "Object" : ObjGetCapacity(value) != "") {
					if (this.gap) {
						stepback := this.indent
						this.indent .= this.gap
					}

					is_array := value.IsArray
				; Array() is not overridden, rollback to old method of
				; identifying array-like objects. Due to the use of a for-loop
				; sparse arrays such as '[1,,3]' are detected as objects({}). 
					if (!is_array) {
						for i in value
							is_array := i == A_Index
						until !is_array
					}

					str := ""
					if (is_array) {
						Loop, % value.Length() {
							if (this.gap)
								str .= this.indent
							
							v := this.Str(value, A_Index)
							str .= (v != "") ? v . "," : "null,"
						}
					} else {
						colon := this.gap ? ": " : ":"
						for k in value {
							v := this.Str(value, k)
							if (v != "") {
								if (this.gap)
									str .= this.indent

								str .= this.Quote(k) . colon . v . ","
							}
						}
					}

					if (str != "") {
						str := RTrim(str, ",")
						if (this.gap)
							str .= stepback
					}

					if (this.gap)
						this.indent := stepback

					return is_array ? "[" . str . "]" : "{" . str . "}"
				}
			
			} else ; is_number ? value : "value"
				return ObjGetCapacity([value], 1)=="" ? value : this.Quote(value)
		}

		Quote(string)
		{
			static quot := Chr(34), bashq := "\" . quot

			if (string != "") {
				  string := StrReplace(string,  "\",  "\\")
				; , string := StrReplace(string,  "/",  "\/") ; optional in ECMAScript
				, string := StrReplace(string, quot, bashq)
				, string := StrReplace(string, "`b",  "\b")
				, string := StrReplace(string, "`f",  "\f")
				, string := StrReplace(string, "`n",  "\n")
				, string := StrReplace(string, "`r",  "\r")
				, string := StrReplace(string, "`t",  "\t")

				static rx_escapable := A_AhkVersion<"2" ? "O)[^\x20-\x7e]" : "[^\x20-\x7e]"
				while RegExMatch(string, rx_escapable, m)
					string := StrReplace(string, m.Value, Format("\u{1:04x}", Ord(m.Value)))
			}

			return quot . string . quot
		}
	}

	/**
	 * Property: Undefined
	 *     Proxy for 'undefined' type
	 * Syntax:
	 *     undefined := CocoJson.Undefined
	 * Remarks:
	 *     For use with reviver and replacer functions since AutoHotkey does not
	 *     have an 'undefined' type. Returning blank("") or 0 won't work since these
	 *     can't be distnguished from actual JSON values. This leaves us with objects.
	 *     Replacer() - the caller may return a non-serializable AHK objects such as
	 *     ComObject, Func, BoundFunc, FileObject, RegExMatchObject, and Property to
	 *     mimic the behavior of returning 'undefined' in JavaScript but for the sake
	 *     of code readability and convenience, it's better to do 'return CocoJson.Undefined'.
	 *     Internally, the property returns a ComObject with the variant type of VT_EMPTY.
	 */
	Undefined[]
	{
		get {
			static empty := {}, vt_empty := ComObject(0, &empty, 1)
			return vt_empty
		}
	}

	class Functor
	{
		__Call(method, ByRef arg, args*)
		{
		; When casting to Call(), use a new instance of the "function object"
		; so as to avoid directly storing the properties(used across sub-methods)
		; into the "function object" itself.
			if IsObject(method)
				return (new this).Call(method, arg, args*)
			else if (method == "")
				return (new this).Call(arg, args*)
		}
	}
}
my, my, what a predicament. which lib do i use?
coco's is slow to load, fast to access, easy to access, doesnt handle null
ur ahk implementation is faster to load, faster to access, easy to access, doesnt handle null, and doesnt handle true apparently
ur js comobj is the fastest to load, slower to access, hard to access(no enum, have to split on separate lines, have to know how much to loop for), handles null, doesnt handle true
ur json string getkey has no loading component, slowest to access, easier but still hard to access(same as above but replace splitting with trimming and string concat), handles null and true
ur json enum has no loading component, faster but still slow to access, easier still but still hard to access(wrap with enum, getkey, trimming), handles null and true
Last edited by swagfag on 27 Jun 2019, 03:36, edited 1 time in total.
afe
Posts: 615
Joined: 06 Dec 2018, 04:36

Re: Is there a simple and reliable way to process JSON data?

25 Jun 2019, 00:15

teadrinker wrote:
24 Jun 2019, 06:21
A simpler way:

Code: Select all

doc := ComObjCreate("htmlfile")
doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
MsgBox, % doc.parentWindow.eval("(" . JSON . ").data[0].Name")
Thank you very much. This makes it much easier to extract data!

I have been thinking about which language is more suitable for writing web-oriented and GUI-based desktop programs, mainly to send GET/POST requests and receive responses.

I started to contact AHK from a very small AHK mouse automatic click program. I am currently using AHK to write a program that primarily handles web requests and responses. It does not use any automatic click or automated processing features. This seems to deviate from the purpose of AHK.

Getting started with AHK is not too painful, and writing a GUI is easier and more flexible. But sometimes it can be tricky, such as too few functions, and often have to write it yourself. Such as Percent-encoding, Base64 codec and so on. The next goal is that I have to use AHK to implement the MD5 algorithm.

I learned about other languages, it seems that C# is a direction, but I am not sure. Although I know about it, I have not learned it. I have only studied C. For C++, I only saw inheritance there, and didn't continue to look down. For object-oriented programming, I learned some preliminary concepts from C++, but there is still a lot to learn.
 
Is there any good advice?
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Is there a simple and reliable way to process JSON data?

25 Jun 2019, 09:24

swagfag wrote: doesnt handle null, and doesnt handle true apparently
AHK does not support null and true, what's the point of talking about it?
swagfag wrote: ur js comobj ... doesnt handle true apparently
How did you check it?
Anyway, thank you for your investigation, although your code gave me a strange result:

Image

As I understand, allthings Coco's library can do, mine does faster?

@afe
Learning other programming languages ​​is useful for expanding your horizons, but I don't believe that AHK does not meet your needs. All tasks you mentioned could be solved with AHK. But if you want to progress as a programmer, learn other languages, of course. :)
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Is there a simple and reliable way to process JSON data?

25 Jun 2019, 16:22

the results are given in terms of ticks and milliseconds. dont compare ur tick results to mine. ur computer's clock frequency might be different, only compare the results between themselves(but the milliseconds should be universally comparable)

the takeaway is:
cocslib takes a long time to generate the ahk object, whereas ur ahk implementation does it fast. the js ComObject implementation does it the fastest.
key access is comparable across all 3 implementations.
cocoslib mangles null into "", ur ahk and js comobj implementations do so as well. furthermore, ur ahk and js comobj implementations parse the JS true as an AHK -1 <--- wrong

the json string GetKey and enum with json strings handle all json values correctly(because everything is processed on the JS side, duhh), but are orders of magnitude slower to work with
what do u mean whats the point of talking about it? u brought the fact that cocoslib didnt handle null when saving back into json, so clearly there must have been some merit to talking about it. im just going along with it. also, ahk absolutely does have true, its supposed to be 1.
how did i test it? like so:

Code: Select all

jsonString = {"key1": true}
TeaJsObj := JSON.Parse(jsonString, true)
MsgBox % TeaJsObj.key1 ; -1

class JSON
{
   static JS := JSON._GetJScriptObject()
   
   Parse(sJson, js := false)  {
      if jsObj := this.VerifyJson(sJson)
         Return js ? jsObj : this._CreateObject(jsObj)
   }
   
   Stringify(obj, js := false) {
      if js
         Return this.JS.JSON.stringify(obj)
      else {
         sObj := this._ObjToString(obj)
         Return this.JS.eval("JSON.stringify(" . sObj . ")")
      }
   }
   
   GetKey(sJson, key) {
      if !this.VerifyJson(sJson)
         Return
      try res := this.JS.eval("JSON.stringify((" . sJson . ")" . (SubStr(key, 1, 1) = "[" ? "" : ".") . key . ")")
      catch {
         MsgBox, Wrong key:`n`n%key%
         Return
      }
      Return res
   }
   
   Enum(sJson, key := "") {
      if !this.VerifyJson(sJson)
         Return
      
      sign := key ? (SubStr(key, 1, 1) = "[" ? "" : ".") . key : ""
      try jsObj := this.JS.eval("(" sJson ")" . sign)
      catch {
         MsgBox, Wrong key:`n`n%key%
         Return
      }
      res := jsObj.IsArray()
      if (res = "")
         Return
      obj := {}
      if (res = -1)  {
         Loop % jsObj.length
            obj[A_Index - 1] := this.JS.eval("JSON.stringify((" sJson ")" . sign . "[" . (A_Index - 1) . "])")
      }
      else if (res = 0) {
         keys := jsObj.GetKeys()
         Loop % keys.length
            k := keys[A_Index - 1], obj[k] := this.JS.eval("JSON.stringify((" sJson ")" . sign . "." . k . ")")
      }
      Return obj
   }
   
   SetKey(sJson, key, value) {
      if !(this.VerifyJson(sJson) && this.VerifyJson(value))
         Return
      res := this.JS.eval( "var obj = (" . sJson . ");"
                         . "obj" . (SubStr(key, 1, 1) = "[" ? "" : ".") . key . "=" . value . ";"
                         . "JSON.stringify(obj)" )
      this.JS.eval("obj = ''")
      Return res
   }
   
   RemoveKey(sJson, key) {
      if !this.VerifyJson(sJson)
         Return
      
      sign := key ? (SubStr(key, 1, 1) = "[" ? "" : ".") . key : ""
      if RegExMatch(key, "(.*)\[(\d+)]$", match)
         res := this.JS.eval( "var obj = (" . sJson . ");" 
                            . "obj" . (match1 != "" ? sign . match1 : "") . ".splice(" . match2 . ", 1); JSON.stringify(obj)" )
      else
         res := this.JS.eval("var obj = (" . sJson . "); delete obj." . key . "; JSON.stringify(obj)")
      this.JS.eval("obj = ''")
      Return res
   }
   
   VerifyJson(sJson) {
      try jsObj := this.JS.eval("(" sJson ")")
      catch {
         MsgBox, Wrong JSON string:`n`n%sJson%
         Return
      }
      Return IsObject(jsObj) ? jsObj : true
   }
   
   _ObjToString(obj) {
      if IsObject( obj )  {
         isArray := true
         for key in obj {
            if IsObject(key)
               throw Exception("Invalid key")
            if !( key = A_Index || isArray := false )
               break
         }
         for k, v in obj
            str .= ( A_Index = 1 ? "" : "," ) . ( isArray ? "" : k . ":" ) . this._ObjToString(v)

         Return isArray ? "[" str "]" : "{" str "}"
      }
      else if !(obj*1 = "" || RegExMatch(obj, "\s"))
         Return obj
      else
         Return """" obj """"
   }

   _GetJScriptObject()  {
      static doc
      doc := ComObjCreate("htmlfile")
      doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
      JS := doc.parentWindow
      JSON._AddMethods(JS)
      Return JS
   }

   _AddMethods(ByRef JS)  {
      JScript =
      (
         Object.prototype.GetKeys = function () {
            var keys = []
            for (var k in this)
               if (this.hasOwnProperty(k))
                  keys.push(k)
            return keys
         }
         Object.prototype.IsArray = function () {
            var toStandardString = {}.toString
            return toStandardString.call(this) == '[object Array]'
         }
      )
      JS.eval(JScript)
   }

   _CreateObject(ObjJS)  {
      res := ObjJS.IsArray()
      if (res = "")
         Return ObjJS
      
      else if (res = -1)  {
         obj := []
         Loop % ObjJS.length
            obj[A_Index] := this._CreateObject(ObjJS[A_Index - 1])
      }
      else if (res = 0)  {
         obj := {}
         keys := ObjJS.GetKeys()
         Loop % keys.length
            k := keys[A_Index - 1], obj[k] := this._CreateObject(ObjJS[k])
      }
      Return obj
   }
}
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Is there a simple and reliable way to process JSON data?

25 Jun 2019, 16:51

swagfag wrote:

Code: Select all

jsonString = {"key1": true}
TeaJsObj := JSON.Parse(jsonString, true)
MsgBox % TeaJsObj.key1 ; -1
You just check not properly.

Code: Select all

jsonString = {"key1": true}
TeaJsObj := JSON.Parse(jsonString, true)
MsgBox % TeaJsObj.key1                      ; -1, but it's AHK's interpretation of js true
MsgBox, % JSON.Stringify(TeaJsObj, true)    ; {"key1":true}
swagfag wrote: ahk absolutely does have true, its supposed to be 1.
Yes, but in Javascript true is not 1, it is not a numeric value.

Code: Select all

doc := ComObjCreate("htmlfile")
doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
doc.parentWindow.execScript("alert(true === 1)")
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Is there a simple and reliable way to process JSON data?

25 Jun 2019, 16:59

???
true in AHK is 1, observe:

Code: Select all

MsgBox % true + true ; 2
true in JS is of type Boolean, im not contesting that. However, the JS-true is being parsed incorrectly and interpreted incorrectly as -1 in AHK
i feel like im repeating myself here
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Is there a simple and reliable way to process JSON data?

25 Jun 2019, 17:13

swagfag wrote: the JS-true is being parsed incorrectly and interpreted incorrectly as -1 in AHK
Well yes! Why AHK should handle js true, if in AHK there isn't boolean variables? JS object is not intended for parsing in AHK, it is needed only for intermediate calculations.
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Is there a simple and reliable way to process JSON data?

27 Jun 2019, 03:33

well, if u agree that there are JS types that cannot be mapped 1:1 in AHK, why then did u take issue with the fact that cocoslib mangled null upon saving?
i thought the point of this exercise was to find a way to handle JSON in AHK that is fast, easy to use and stays truthful to the source JSON.
it seems this is impossible: u can either have fast and easy but not correct, or u can have correct but not fast and not easy
regardless, ur lib is at least better than cocojson in that it offers users the possibility to choose, which implementation best fits their needs, as i had already mentioned

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: FanaticGuru, mikeyww, OrangeCat and 128 guests