Advertisement
comniemeer

JSON.ahk

May 11th, 2017
280
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  * Lib: JSON.ahk
  3.  *     JSON lib for AutoHotkey.
  4.  * Version:
  5.  *     v2.1.3 [updated 04/18/2016 (MM/DD/YYYY)]
  6.  * License:
  7.  *     WTFPL [http://wtfpl.net/]
  8.  * Requirements:
  9.  *     Latest version of AutoHotkey (v1.1+ or v2.0-a+)
  10.  * Installation:
  11.  *     Use #Include JSON.ahk or copy into a function library folder and then
  12.  *     use #Include <JSON>
  13.  * Links:
  14.  *     GitHub:     - https://github.com/cocobelgica/AutoHotkey-JSON
  15.  *     Forum Topic - http://goo.gl/r0zI8t
  16.  *     Email:      - cocobelgica <at> gmail <dot> com
  17.  */
  18.  
  19.  
  20. /**
  21.  * Class: JSON
  22.  *     The JSON object contains methods for parsing JSON and converting values
  23.  *     to JSON. Callable - NO; Instantiable - YES; Subclassable - YES;
  24.  *     Nestable(via #Include) - NO.
  25.  * Methods:
  26.  *     Load() - see relevant documentation before method definition header
  27.  *     Dump() - see relevant documentation before method definition header
  28.  */
  29. class JSON
  30. {
  31.     /**
  32.      * Method: Load
  33.      *     Parses a JSON string into an AHK value
  34.      * Syntax:
  35.      *     value := JSON.Load( text [, reviver ] )
  36.      * Parameter(s):
  37.      *     value      [retval] - parsed value
  38.      *     text    [in, ByRef] - JSON formatted string
  39.      *     reviver   [in, opt] - function object, similar to JavaScript's
  40.      *                           JSON.parse() 'reviver' parameter
  41.      */
  42.     class Load extends JSON.Functor
  43.     {
  44.         Call(self, ByRef text, reviver:="")
  45.         {
  46.             this.rev := IsObject(reviver) ? reviver : false
  47.         ; Object keys(and array indices) are temporarily stored in arrays so that
  48.         ; we can enumerate them in the order they appear in the document/text instead
  49.         ; of alphabetically. Skip if no reviver function is specified.
  50.             this.keys := this.rev ? {} : false
  51.  
  52.             static quot := Chr(34), bashq := "\" . quot
  53.                  , json_value := quot . "{[01234567890-tfn"
  54.                  , json_value_or_array_closing := quot . "{[]01234567890-tfn"
  55.                  , object_key_or_object_closing := quot . "}"
  56.  
  57.             key := ""
  58.             is_key := false
  59.             root := {}
  60.             stack := [root]
  61.             next := json_value
  62.             pos := 0
  63.  
  64.             while ((ch := SubStr(text, ++pos, 1)) != "") {
  65.                 if InStr(" `t`r`n", ch)
  66.                     continue
  67.                 if !InStr(next, ch, 1)
  68.                     this.ParseError(next, text, pos)
  69.  
  70.                 holder := stack[1]
  71.                 is_array := holder.IsArray
  72.  
  73.                 if InStr(",:", ch) {
  74.                     next := (is_key := !is_array && ch == ",") ? quot : json_value
  75.  
  76.                 } else if InStr("}]", ch) {
  77.                     ObjRemoveAt(stack, 1)
  78.                     next := stack[1]==root ? "" : stack[1].IsArray ? ",]" : ",}"
  79.  
  80.                 } else {
  81.                     if InStr("{[", ch) {
  82.                     ; Check if Array() is overridden and if its return value has
  83.                     ; the 'IsArray' property. If so, Array() will be called normally,
  84.                     ; otherwise, use a custom base object for arrays
  85.                         static json_array := Func("Array").IsBuiltIn || ![].IsArray ? {IsArray: true} : 0
  86.                    
  87.                     ; sacrifice readability for minor(actually negligible) performance gain
  88.                         (ch == "{")
  89.                             ? ( is_key := true
  90.                               , value := {}
  91.                               , next := object_key_or_object_closing )
  92.                         ; ch == "["
  93.                             : ( value := json_array ? new json_array : []
  94.                               , next := json_value_or_array_closing )
  95.                        
  96.                         ObjInsertAt(stack, 1, value)
  97.  
  98.                         if (this.keys)
  99.                             this.keys[value] := []
  100.                    
  101.                     } else {
  102.                         if (ch == quot) {
  103.                             i := pos
  104.                             while (i := InStr(text, quot,, i+1)) {
  105.                                 value := StrReplace(SubStr(text, pos+1, i-pos-1), "\\", "\u005c")
  106.  
  107.                                 static tail := A_AhkVersion<"2" ? 0 : -1
  108.                                 if (SubStr(value, tail) != "\")
  109.                                     break
  110.                             }
  111.  
  112.                             if (!i)
  113.                                 this.ParseError("'", text, pos)
  114.  
  115.                               value := StrReplace(value,  "\/",  "/")
  116.                             , value := StrReplace(value, bashq, quot)
  117.                             , value := StrReplace(value,  "\b", "`b")
  118.                             , value := StrReplace(value,  "\f", "`f")
  119.                             , value := StrReplace(value,  "\n", "`n")
  120.                             , value := StrReplace(value,  "\r", "`r")
  121.                             , value := StrReplace(value,  "\t", "`t")
  122.  
  123.                             pos := i ; update pos
  124.                            
  125.                             i := 0
  126.                             while (i := InStr(value, "\",, i+1)) {
  127.                                 if !(SubStr(value, i+1, 1) == "u")
  128.                                     this.ParseError("\", text, pos - StrLen(SubStr(value, i+1)))
  129.  
  130.                                 uffff := Abs("0x" . SubStr(value, i+2, 4))
  131.                                 if (A_IsUnicode || uffff < 0x100)
  132.                                     value := SubStr(value, 1, i-1) . Chr(uffff) . SubStr(value, i+6)
  133.                             }
  134.  
  135.                             if (is_key) {
  136.                                 key := value, next := ":"
  137.                                 continue
  138.                             }
  139.                        
  140.                         } else {
  141.                             value := SubStr(text, pos, i := RegExMatch(text, "[\]\},\s]|$",, pos)-pos)
  142.  
  143.                             static number := "number", integer :="integer"
  144.                             if value is %number%
  145.                             {
  146.                                 if value is %integer%
  147.                                     value += 0
  148.                             }
  149.                             else if (value == "true" || value == "false")
  150.                                 value := %value% + 0
  151.                             else if (value == "null")
  152.                                 value := ""
  153.                             else
  154.                             ; we can do more here to pinpoint the actual culprit
  155.                             ; but that's just too much extra work.
  156.                                 this.ParseError(next, text, pos, i)
  157.  
  158.                             pos += i-1
  159.                         }
  160.  
  161.                         next := holder==root ? "" : is_array ? ",]" : ",}"
  162.                     } ; If InStr("{[", ch) { ... } else
  163.  
  164.                     is_array? key := ObjPush(holder, value) : holder[key] := value
  165.  
  166.                     if (this.keys && this.keys.HasKey(holder))
  167.                         this.keys[holder].Push(key)
  168.                 }
  169.            
  170.             } ; while ( ... )
  171.  
  172.             return this.rev ? this.Walk(root, "") : root[""]
  173.         }
  174.  
  175.         ParseError(expect, ByRef text, pos, len:=1)
  176.         {
  177.             static quot := Chr(34), qurly := quot . "}"
  178.            
  179.             line := StrSplit(SubStr(text, 1, pos), "`n", "`r").Length()
  180.             col := pos - InStr(text, "`n",, -(StrLen(text)-pos+1))
  181.             msg := Format("{1}`n`nLine:`t{2}`nCol:`t{3}`nChar:`t{4}"
  182.             ,     (expect == "")     ? "Extra data"
  183.                 : (expect == "'")    ? "Unterminated string starting at"
  184.                 : (expect == "\")    ? "Invalid \escape"
  185.                 : (expect == ":")    ? "Expecting ':' delimiter"
  186.                 : (expect == quot)   ? "Expecting object key enclosed in double quotes"
  187.                 : (expect == qurly)  ? "Expecting object key enclosed in double quotes or object closing '}'"
  188.                 : (expect == ",}")   ? "Expecting ',' delimiter or object closing '}'"
  189.                 : (expect == ",]")   ? "Expecting ',' delimiter or array closing ']'"
  190.                 : InStr(expect, "]") ? "Expecting JSON value or array closing ']'"
  191.                 :                      "Expecting JSON value(string, number, true, false, null, object or array)"
  192.             , line, col, pos)
  193.  
  194.             static offset := A_AhkVersion<"2" ? -3 : -4
  195.             throw Exception(msg, offset, SubStr(text, pos, len))
  196.         }
  197.  
  198.         Walk(holder, key)
  199.         {
  200.             value := holder[key]
  201.             if IsObject(value) {
  202.                 for i, k in this.keys[value] {
  203.                     ; check if ObjHasKey(value, k) ??
  204.                     v := this.Walk(value, k)
  205.                     if (v != JSON.Undefined)
  206.                         value[k] := v
  207.                     else
  208.                         ObjDelete(value, k)
  209.                 }
  210.             }
  211.            
  212.             return this.rev.Call(holder, key, value)
  213.         }
  214.     }
  215.  
  216.     /**
  217.      * Method: Dump
  218.      *     Converts an AHK value into a JSON string
  219.      * Syntax:
  220.      *     str := JSON.Dump( value [, replacer, space ] )
  221.      * Parameter(s):
  222.      *     str        [retval] - JSON representation of an AHK value
  223.      *     value          [in] - any value(object, string, number)
  224.      *     replacer  [in, opt] - function object, similar to JavaScript's
  225.      *                           JSON.stringify() 'replacer' parameter
  226.      *     space     [in, opt] - similar to JavaScript's JSON.stringify()
  227.      *                           'space' parameter
  228.      */
  229.     class Dump extends JSON.Functor
  230.     {
  231.         Call(self, value, replacer:="", space:="")
  232.         {
  233.             this.rep := IsObject(replacer) ? replacer : ""
  234.  
  235.             this.gap := ""
  236.             if (space) {
  237.                 static integer := "integer"
  238.                 if space is %integer%
  239.                     Loop, % ((n := Abs(space))>10 ? 10 : n)
  240.                         this.gap .= " "
  241.                 else
  242.                     this.gap := SubStr(space, 1, 10)
  243.  
  244.                 this.indent := "`n"
  245.             }
  246.  
  247.             return this.Str({"": value}, "")
  248.         }
  249.  
  250.         Str(holder, key)
  251.         {
  252.             value := holder[key]
  253.  
  254.             if (this.rep)
  255.                 value := this.rep.Call(holder, key, ObjHasKey(holder, key) ? value : JSON.Undefined)
  256.  
  257.             if IsObject(value) {
  258.             ; Check object type, skip serialization for other object types such as
  259.             ; ComObject, Func, BoundFunc, FileObject, RegExMatchObject, Property, etc.
  260.                 static type := A_AhkVersion<"2" ? "" : Func("Type")
  261.                 if (type ? type.Call(value) == "Object" : ObjGetCapacity(value) != "") {
  262.                     if (this.gap) {
  263.                         stepback := this.indent
  264.                         this.indent .= this.gap
  265.                     }
  266.  
  267.                     is_array := value.IsArray
  268.                 ; Array() is not overridden, rollback to old method of
  269.                 ; identifying array-like objects. Due to the use of a for-loop
  270.                 ; sparse arrays such as '[1,,3]' are detected as objects({}).
  271.                     if (!is_array) {
  272.                         for i in value
  273.                             is_array := i == A_Index
  274.                         until !is_array
  275.                     }
  276.  
  277.                     str := ""
  278.                     if (is_array) {
  279.                         Loop, % value.Length() {
  280.                             if (this.gap)
  281.                                 str .= this.indent
  282.                            
  283.                             v := this.Str(value, A_Index)
  284.                             str .= (v != "") ? v . "," : "null,"
  285.                         }
  286.                     } else {
  287.                         colon := this.gap ? ": " : ":"
  288.                         for k in value {
  289.                             v := this.Str(value, k)
  290.                             if (v != "") {
  291.                                 if (this.gap)
  292.                                     str .= this.indent
  293.  
  294.                                 str .= this.Quote(k) . colon . v . ","
  295.                             }
  296.                         }
  297.                     }
  298.  
  299.                     if (str != "") {
  300.                         str := RTrim(str, ",")
  301.                         if (this.gap)
  302.                             str .= stepback
  303.                     }
  304.  
  305.                     if (this.gap)
  306.                         this.indent := stepback
  307.  
  308.                     return is_array ? "[" . str . "]" : "{" . str . "}"
  309.                 }
  310.            
  311.             } else ; is_number ? value : "value"
  312.                 return ObjGetCapacity([value], 1)=="" ? value : this.Quote(value)
  313.         }
  314.  
  315.         Quote(string)
  316.         {
  317.             static quot := Chr(34), bashq := "\" . quot
  318.  
  319.             if (string != "") {
  320.                   string := StrReplace(string,  "\",  "\\")
  321.                 ; , string := StrReplace(string,  "/",  "\/") ; optional in ECMAScript
  322.                 , string := StrReplace(string, quot, bashq)
  323.                 , string := StrReplace(string, "`b",  "\b")
  324.                 , string := StrReplace(string, "`f",  "\f")
  325.                 , string := StrReplace(string, "`n",  "\n")
  326.                 , string := StrReplace(string, "`r",  "\r")
  327.                 , string := StrReplace(string, "`t",  "\t")
  328.  
  329.                 static rx_escapable := A_AhkVersion<"2" ? "O)[^\x20-\x7e]" : "[^\x20-\x7e]"
  330.                 while RegExMatch(string, rx_escapable, m)
  331.                     string := StrReplace(string, m.Value, Format("\u{1:04x}", Ord(m.Value)))
  332.             }
  333.  
  334.             return quot . string . quot
  335.         }
  336.     }
  337.  
  338.     /**
  339.      * Property: Undefined
  340.      *     Proxy for 'undefined' type
  341.      * Syntax:
  342.      *     undefined := JSON.Undefined
  343.      * Remarks:
  344.      *     For use with reviver and replacer functions since AutoHotkey does not
  345.      *     have an 'undefined' type. Returning blank("") or 0 won't work since these
  346.      *     can't be distnguished from actual JSON values. This leaves us with objects.
  347.      *     Replacer() - the caller may return a non-serializable AHK objects such as
  348.      *     ComObject, Func, BoundFunc, FileObject, RegExMatchObject, and Property to
  349.      *     mimic the behavior of returning 'undefined' in JavaScript but for the sake
  350.      *     of code readability and convenience, it's better to do 'return JSON.Undefined'.
  351.      *     Internally, the property returns a ComObject with the variant type of VT_EMPTY.
  352.      */
  353.     Undefined[]
  354.     {
  355.         get {
  356.             static empty := {}, vt_empty := ComObject(0, &empty, 1)
  357.             return vt_empty
  358.         }
  359.     }
  360.  
  361.     class Functor
  362.     {
  363.         __Call(method, ByRef arg, args*)
  364.         {
  365.         ; When casting to Call(), use a new instance of the "function object"
  366.         ; so as to avoid directly storing the properties(used across sub-methods)
  367.         ; into the "function object" itself.
  368.             if IsObject(method)
  369.                 return (new this).Call(method, arg, args*)
  370.             else if (method == "")
  371.                 return (new this).Call(arg, args*)
  372.         }
  373.     }
  374. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement