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

Get help with using AutoHotkey and its commands and hotkeys
teadrinker
Posts: 1018
Joined: 29 Mar 2015, 09:41
Contact:

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

27 Jun 2019, 10:35

swagfag wrote: ur lib is at least better than cocojson in that it offers users the possibility to choose, which implementation best fits their needs
This is not the only difference.

Code: Select all

obj := {en: "hi", ru: "привет"}
MsgBox, % JSON.Stringify(obj)
MsgBox, % CocoJson.Dump(obj)

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
   }
}


/**
 * 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*)
      }
   }
}
swagfag wrote: u can have correct but not fast
I don't consider speed as an important factor in this case. Speed ​​is important when we need to do something many times in a cicle, but JSON is used for one-time actions like get value, set value, save values.
swagfag
Posts: 2981
Joined: 11 Jan 2017, 17:59

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

27 Jun 2019, 12:47

of course, speed depends on the use case, but it isnt something to ignore either. if there are many json files that need loaded, or a single big one, or a single one that needs constantly reloaded, then i sure wouldn't want to spend the rest of eternity waiting on GetKey to return
teadrinker
Posts: 1018
Joined: 29 Mar 2015, 09:41
Contact:

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

27 Jun 2019, 19:27

When you give a real example, then we'll talk. :)
swagfag
Posts: 2981
Joined: 11 Jan 2017, 17:59

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

28 Jun 2019, 05:25

is the example provided not real enough? ignore the fact that its generated and pretend it was a thousand element collection returned by a web api or a shitty single player rpg's save file as json with all ur characters, with all their items and whatnot in it. doubt ud fancy going through it with GetKey
teadrinker
Posts: 1018
Joined: 29 Mar 2015, 09:41
Contact:

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

28 Jun 2019, 11:46

swagfag wrote: pretend it was a thousand element collection
If it was a thousand element collection and my library was to slow to handle it, I would think about something else. For the file you provided it's speed quite enough, that's all.
swagfag
Posts: 2981
Joined: 11 Jan 2017, 17:59

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

28 Jun 2019, 12:03

well whatever, this thread has run its course
ill acknowledge that ur lib also handles slavic runes whereas cocos does not(although i havent run the examples, just going off of what u said) and we can conclude this
User avatar
niczoom
Posts: 76
Joined: 09 Mar 2016, 22:17

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

28 Jun 2019, 21:44

How about this:

Code: Select all

json=
(
{
"data":[
{
  "Name": "John",
  "phone": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "mobile",
      "number": "123 456-7890"
    }
  ]
},
{
  "Name": "Mike",
  "phone": [
    {
      "type": "home",
      "number": "212 555-5678"
    },
    {
      "type": "mobile",
      "number": "123 456-1234"
    }
  ]
}]
}
)

#q::
	obj:=jsonAHK(json)
	Try
	{
		MsgBox % obj.data.0.phone.1.number
		MsgBox % obj.data.1.phone.0.type
		MsgBox % obj.data.1.name ; Creates an exeption as the 'name' is case sensitive - Name
	}
	catch e
	{
		MsgBox, An exception was thrown
		ExitApp
	}
	return

jsonAHK(s){
	; https://autohotkey.com/board/topic/95262-obj-json-obj/
	static o:=comobjcreate("scriptcontrol")
	o.language:="jscript"
	return o.eval("(" s ")")
}
[AHK] 1.1.23.05 x32 Unicode
[WIN] 10 Pro x64 Version 5111 (Build 10586.218)
teadrinker
Posts: 1018
Joined: 29 Mar 2015, 09:41
Contact:

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

29 Jun 2019, 02:48

The main disadvantage is the lack of scriptcontrol in 64-bit AHK.

Return to “Ask For Help”

Who is online

Users browsing this forum: asad41163, chubbychub, flyingDman, Jovannb, KHA, mikegt2, TAC109, Xtra and 200 guests