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