JSON-formatted files and HTMLFile object

Post your working scripts, libraries and tools for AHK v1.1 and older
A_AhkUser
Posts: 1147
Joined: 06 Mar 2017, 16:18
Location: France
Contact:

JSON-formatted files and HTMLFile object

02 May 2017, 22:17

Just found out about HTMLFile object thanks to jeeswg. But, by means of HTMLFile object, it appears that one can operate upon json formatted files (read and write) without even creating an ActiveX control embedded into a GUI. Here's a class that demonstrates the principle.


Save the JSON sample below as settings.json and the class as Class.JSONData.ahk, both in the script's own directory.

manifest.json:

Code: Select all

{
	"[AA]BB[CC]":"test\ttest\r\ntest\ttest"
	,"parameters": [ 0.3, 22, false, false, null ]
	,"some key":19
	,"hotkeys": {
		"keyboard": {
			"hotkeys": {
				"a":"!g"
				,"b":"!q"
				,"c":"!s"
				,"d":"!x"
				,"e":"!NumpadAdd"
				,"f":"!NumpadSub"
			}
		}
		,"joystick": {
			"port":0
			,"parameter":0.3
			,"hotkeys": {
				"a":"Joy12"
				,"b":"Joy11"
				,"c":"Joy10"
				,"d":"Joy1"
				,"e":"Joy6"
				,"f":"Joy5"
			}
		}
	}
	,"options": {
		"outputDebug":false
	}
}

JSONData class:

Code: Select all

Class JSONData {
	
	Init() {
	static __ := JSONData.Init()
	__HTMLFile := "<!DOCTYPE html>"
				. "<html>"
					. "<head>"
					. "<meta http-equiv=""X-UA-Compatible"" content=""IE=edge"">"
					. "<meta charset=""utf-8"" />"
					. "<title>HTMLFile</title>"
					. "<script>"
					. "var _null = null;"
					. "var JSONData = new Array();"
					. "function _delete(__o, __k) {"
					. "delete __o[__k];"
					. "}"
					. "</script>"
					. "</head>"
				. "<body>"
				. "</body>"
				. "</html>"
	(JSONData.oHTML:=ComObjCreate("HTMLFile")).write(__HTMLFile)
	}
	
	__New(__fileFullPath, __restore:=false) {
	
	static __i := -1

		if not (__f:=FileOpen(this.fileFullPath:=__fileFullPath, "r", "utf-8"))
	return false, ErrorLevel := (A_LastError == 2) ? "ERROR_FILE_NOT_FOUND" : (A_LastError == 3) ? "ERROR_PATH_NOT_FOUND" : A_LastError ; https://msdn.microsoft.com/fr-fr/library/windows/desktop/ms681382(v=vs.85).aspx
	
		try {
		(JSONData.oHTML.parentWindow.JSONData)[ (__restore) ? this.index : this.index:=++__i ] := JSONData.parse(__f.Read())
		} catch {
			__f.Close()
		return false, ErrorLevel:="ERROR_PARSE_ERROR"
		}
		__f.Close()
	
	return this
	}
	
		Class Enumerator {
			
			i := -1
			
			__New(__object) {
			try this.count := (this.keys:=JSONData.oHTML.parentWindow.Object.keys(this.object:=__object).slice()).length
			}
			next(ByRef __k:="", ByRef __v:="") {
				
				if (++this.i < this.count) {
					__k := (this.keys)[ this.i ], __v := (this.object)[__k]
				return true
				} return false, this.i:=-1
			
			}
		
		}
		
		stringify(__obj, __space:="") {
		return JSONData.oHTML.parentWindow.JSON.stringify(__obj,, __space)
		}
		parse(__str) {
		return JSONData.oHTML.parentWindow.JSON.parse(__str)
		}
		
			delete(__o, __k) {
			JSONData.oHTML.parentWindow._delete(__o, __k)
			}
		
		; ___________________________________________
		
		Class String {
		__New() {
		return JSONData.oHTML.parentWindow.String()
		}
		}
		Class Number {
		__New() {
		return JSONData.oHTML.parentWindow.Number()
		}
		}
		Class Object {
		__New() {
		return JSONData.oHTML.parentWindow.Object()
		}
		}
		Class Array {
		__New() {
		return JSONData.oHTML.parentWindow.Array()
		}
		}
		
		; ___________________________________________
		
		restore() {
		this := this.__New(this.fileFullPath, true)
		}
		updateData(__prettify:=4) {
		(__f:=FileOpen(this.fileFullPath, "w", "utf-8")).Write(JSONData.stringify(this.__data__, __prettify))
		__f.Close()
		}
	
			__data__[__args*] {
				get {
				return (JSONData.oHTML.parentWindow.JSONData)[ this.index ]
				}
				set {
				(JSONData.oHTML.parentWindow.JSONData)[ this.index ] := value
				}
			}
			
	getKeys(__prm*) {
	__o := this.__data__
		for __k, __v in __prm
			__o := (__o)[__v]
	return JSONData.oHTML.parentWindow.Object.getOwnPropertyNames(__o)
	}

}

Example:

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
#Warn  ; Enable warnings to assist with detecting common errors.
#SingleInstance, force


#Include %A_ScriptDir%\Class.JSONData.ahk


SETTINGS_FILE := A_ScriptDir . "\settings.json"
Settings := new JSONData(SETTINGS_FILE) ; create a new instance of JSONData base object
if (ErrorLevel) { ; specifically, ErrorLevel is set to 'ERROR_FILE_NOT_FOUND' if the file doesn't exist and to 'ERROR_PARSE_ERROR' if the file is badly formatted
	MsgBox, 64,, % "Could not read or parse: " . SETTINGS_FILE . "`r`nSpecifically: " . ErrorLevel . "`r`n`r`nThe program will exit."
ExitApp
}
data := Settings.__data__ ; get a reference to the data themselves

; =========== retrieve a specific datum ===========
MsgBox % JSONData.stringify(data, A_Tab) ; displays all data as a string using the stringify base method; A_Tab will insert a tab character into the output JSON string for readability purposes
MsgBox % data.options.outputDebug ; the dot is the easiest way to retrieve a specific datum
MsgBox % (data.parameters)[0] ; using brackets, the parenthesis are required when used in combination with the dot syntax; also, indexes start with 0
MsgBox % (data.parameters).join("~") ; each time  an array or object is retrieved it is question of the javascript one so one can operate upon it using javascript methods supported by the type of the value retrieved
MsgBox % data["some key"] ; if key contains spaces, it must be enclosed in brackets
MsgBox % data["[AA]BB[CC]"] ; brackets are  here required to avoid an ambiguous use of the dot > .[

; =========== operate upon data using a enumerator object ===========
enum := new JSONData.Enumerator(data.hotkeys.keyboard.hotkeys) ; enumerator object similar to the ahk one, usefull to enumerate all items in a collection
while enum.next(k, v)
	MsgBox % k . "," . v
	
; =========== delete data ===========
JSONData.delete(data, "hotkeys") ; let's delete some key using the delete base method
MsgBox % JSONData.stringify(data) ; displays data as a string to see changes
Settings.restore() ; undo changes
data := Settings.__data__ ; restore data which still contains the previous data
MsgBox % JSONData.stringify(data)

; =========== retrieve keys ===========
keys := Settings.getKeys("hotkeys", "keyboard", "hotkeys") ; get all the key which belongs to an associative array using the getKeys instance method
MsgBox % keys.join("|") ; one can use the join javascript method upon string values (usefull to populate a DropDownList from a settings file for example)
Loop % keys.length ; one must use javascript length property and not the eponymous ahk method since getKeys return a javascript array
{
MsgBox % keys[ a_index - 1 ] ; same as above: indexes start with 0
}

; =========== create and save various type of data ===========
if (Settings.getKeys().indexOf("test_array_of_objects") = -1) ; if the key 'test_array_of_objects' still not exists yet...
	data.test_array_of_objects := new JSONData.Array() ; ...create a new array
obj := new JSONData.Object() ; create a new object
obj.a := "some text"
obj.b := 3
obj.c := ""
obj.d := false
obj.e := new JSONData.Object()
obj.f := new JSONData.Array()
data.test_array_of_objects.push(obj) ; appends the object to the array
Settings.updateData() ; updates the file
run, notepad %SETTINGS_FILE% ; run the files to see changes
my scripts

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 233 guests