JSON-Lib, read, write within var type quote

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
WarpRider
Posts: 6
Joined: 25 Jun 2020, 09:03

JSON-Lib, read, write within var type quote

Post by WarpRider » 05 Feb 2023, 12:17

Please help me to modify the JSON-Lib from cocobelgica, https://github.com/cocobelgica/AutoHotkey-JSON

My modifications read vars without the type on a string that contains numbers. The JSON String:

Code: Select all

{"ffloatnumber":0.5,"fnumberNullDotNull":0.0,"fnumberten":10,"fonenull":null,"frawfalse":false,"frawtrue":true,"frawtxt":"rawtxt","ftxtnumber":"1234567890"}
after reading:
  • oStr.frawtrue=true
    oStr.frawfalse=false
    oStr.fonenull=null
    oStr.fnumberten=10
    oStr.fnumberNullDotNull=0.0
    oStr.ffloatnumber=0.5
    oStr.ftxtnumber=1234567890 <==== this is a string that contains numbers
    oStr.frawtxt=rawtxt
The problem comes if i write the object to a JSON-file, ftxtnumber will be 1234567890 without quotes.
In general its better the read function load the vars with quotes if there ones.

testing code:

Code: Select all

#Include %A_ScriptDir%\Module\JSON.ahk			;JSON lib for AutoHotkey

;########## JSON Wirte debugging####################################################################
FileRead iStr, %A_ScriptDir%\file.json
oStr := JSON.Load(iStr)

strTGRJResult := " ;einleitend "
(
oStr.frawtrue=" oStr.frawtrue "
oStr.frawfalse=" oStr.frawfalse "
oStr.fonenull=" oStr.fonenull "
oStr.fnumberten=" oStr.fnumberten "
oStr.fnumberNullDotNull=" oStr.fnumberNullDotNull "
oStr.ffloatnumber=" oStr.ffloatnumber "
oStr.ftxtnumber=" oStr.ftxtnumber "
oStr.frawtxt=" oStr.frawtxt "
`n
) " ;ausleitend "
MsgBox, %strTGRJResult%

;Modify some vars
;oStr.ffloatnumber := 0.9

iStr:= JSON.Dump(oStr)

;write JSON to a new file
Filedelete, %A_ScriptDir%\file2.json
FileAppend , %iStr%, %A_ScriptDir%\file2.json

ExitApp, 0
;###################################################################################################

moded lib:

Code: Select all

/**
 * 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
 *
 * Modify by WarpRider, Member on https://www.autohotkey.com/boards
 *     ingnore the ahk internal vars true/false and the string null wil be not empty
 */


/**
 * 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 JSON
{
	/**
	 * 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 JSON.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)) {
							    ;MsgBox, 108.value=#%value%#`n`ntext=%text%
								value := StrReplace(SubStr(text, pos+1, i-pos-1), "\\", "\u005c")
								;value := StrReplace(SubStr(text, pos, i-pos+1), "\\", "\u005c")
								;MsgBox, 111.value=#%value%#`n`ni=%i
								static tail := A_AhkVersion<"2" ? 0 : -1
								if (SubStr(value, tail) != "\")
									break
							}
							;MsgBox, 116.value=#%value%#
							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
							
							strTemp=%strTemp%`n%value%
							;MsgBox, 131.strTemp=%strTemp%						;hier kommen nur Felder außer beim letzen
							i := 0
							while (i := InStr(value, "\",, i+1)) {
							    ;MsgBox, 134.value=#%value%#						;nicht, da hier nur was geht, wenn im value ein \ vorkommt
								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) {
							    ;MsgBox, 144.value=#%value%#						;hier laufen alle keys
								key := value, next := ":"
								continue
							}
						
						} else {
							value := SubStr(text, pos, i := RegExMatch(text, "[\]\},\s]|$",, pos)-pos)
							;MsgBox, 151.value=#%value%#							;hier kommen zwar nur die INhalten, es fehlen "texte"
							
							static number := "number", integer :="integer"
							if value is %number%
							{
								if value is %integer%
									value += 0
							}
							
							;WarpRider 31.01.2023: hier wird value auf true oder false geprüft und behandelt, nach AHK wird das dann 0 oder 1,
							;das ist aber falsch, da true/false für JSON keine boolschen Variablen sind, value muss unverändert übernommen werden
							else if (value == "true" || value == "false")
								value := value	;ORIGINAL: value := %value% + 0
							
							
							else if (value == "null")
								value := "null"									;WarpRider 31.01.2023: hier genauso, warum wird null nicht stur übernommen?
							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 != JSON.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 := JSON.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
	 *                           JSON.stringify() 'replacer' parameter
	 *     space     [in, opt] - similar to JavaScript's JSON.stringify()
	 *                           'space' parameter
	 */
	class Dump extends JSON.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"
			}
			
			;MsgBox, % "vorreturn=" this.Str({"": value}, "")
			return this.Str({"": value}, "")
		}

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

			if (this.rep)
				value := this.rep.Call(holder, key, ObjHasKey(holder, key) ? value : JSON.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 . "}"
				}
			
			}
			;WarpRider 31.01.2023: alle Werte hier, ausser Zahlen werden durch die Funktion Quote() mit " eingefasst,
			;das darf bei true,false,null eben nicht so sein, da true/false für JSON keine boolschen Variablen sind und null nicht leer werden
			else ; is_number ? value : "value"
			{
			;MsgBox, vor.Str.return.raw.value=%value%
			if (value == "true" || value == "false" || value == "null")
			  return value
			if (value < 1 and value > 0)
			  return value
			else
			  return ObjGetCapacity([value], 1)=="" ? value : this.Quote(value)
			}
				
		}

		Quote(string)
		{
		;MsgBox, start.Quote.string=%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)))
			}
			;MsgBox, % "vor.Quote.return=" quot . string . quot
			return quot . string . quot
		}
	}

	/**
	 * Property: Undefined
	 *     Proxy for 'undefined' type
	 * Syntax:
	 *     undefined := JSON.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 JSON.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*)
		}
	}
}


[Mod action: Moved topic from “AutoHotkey v2 Scripts and Functions” because it is a request for help, not a completed script, and it is v1 code, not v2.]

teadrinker
Posts: 4326
Joined: 29 Mar 2015, 09:41
Contact:

Re: JSON-Lib, read, write within var type quote

Post by teadrinker » 05 Feb 2023, 13:29

WarpRider wrote: In general its better the read function load the vars with quotes if there ones.
I don't think it's a good approach. How do you distinguish literal quotes in this case?
WarpRider wrote: ;WarpRider 31.01.2023: hier wird value auf true oder false geprüft und behandelt, nach AHK wird das dann 0 oder 1,
;das ist aber falsch, da true/false für JSON keine boolschen Variablen sind, value muss unverändert übernommen werden
Also not ideal. You assign "true", "false" and "null" as strings, but they are not strings.
It's better migrating to v2, at least it can distinguish numbers from strings.

WarpRider
Posts: 6
Joined: 25 Jun 2020, 09:03

Re: JSON-Lib, read, write, to keep original values

Post by WarpRider » 17 Feb 2023, 12:42

Now i will explain my demmand more better. I'm think it is necessary that a lib like JSON-lib read and write the JSON-Data without any changes on special features from AHK.
Any AHK-code outside of the JSON-lib have to handle the specials of AHK.
In normal case the JSON-lib read the values to a object without format informations. After a write the object to a new JSON-file, the vars will be lost the original format, TRUE and FALSE will be 1 or 0, ect.

At first i was modified the handle from TRUE and FALSE. This two values become not a 1 or 0 rahter the TRUE or FALSE after a write to a new JSON-file.
Second i was modified to read and write a var that contains a "null". Now the NULL value will be untouched after rewrite, and do'snt change to 0 or empty.

At last there is a open MOD within string vars that contain numbers, like "123456789". After writing the var will be a number without quotation marks.

How we can change my moded JSON-lib in this case, to keep that?

Here one example within my MODs:
JSON data on reading:

Code: Select all

{"ffloatnumber":0.5,"fnumberNullDotNull":0.0,"fnumberten":10,"fonenull":null,"frawfalse":false,"frawtrue":true,"frawtxt":"rawtxt","ftxtnumber":"1234567890"}
JSON data avter rewrite to a new file:

Code: Select all

{"ffloatnumber":0.5,"fnumberNullDotNull":0.0,"fnumberten":10,"fonenull":null,"frawfalse":false,"frawtrue":true,"frawtxt":"rawtxt","ftxtnumber":1234567890}

Post Reply

Return to “Ask for Help (v1)”