A customizable solution for serializing AutoHotkey (AHK) object properties, including inherited properties, and/or items into a 100% valid JSON string.
https://www.autohotkey.com/boards/viewtopic.php?f=83&t=137415&p=604407
- Introduction
- Parameters
- Returns
- Options
- Jump to category
- New in v1.3.1
- Enum options
- Callbacks
- Newline and indent options
- Print options
- General options
- StringifyAll.Path
- StringifyAll's process
- Changelog
StringifyAll
works in conjunction with GetPropsInfo
to allow us to include all of an object's properties in the JSON string, not just the items or own properties.
StringifyAll
exposes many options to programmatically restrict what gets included in the JSON string. It also includes options for adjusting the spacing in the string. To set your options, you can:
- Copy the template file into your project directory and set the options using the template.
- Prepare the
ConfigLibrary
class and reference the configuration by name. See the file "templates\ConfigLibrary.ahk". (Added 1.0.3). - Define a class
StringifyAllConfig
anywhere in your code. - Pass an object to the
Options
parameter.
The options defined by the Options
parameter supercede options defined by the StringifyAllConfig
class. This is convenient for setting your own defaults based on your personal preferences / project needs using the class object, and then passing an object to the Options
parameter to adjust your defaults on-the-fly.
Callback functions must not call StringifyAll
. StringifyAll
relies on several variables in the function's scope. Concurrent function calls would change their values, causing unexpected behavior for earlier calls.
For usage examples, see "example\example.ahk".
There are some considerations to keep in mind when using StringifyAll
with the intent to later parse it back into a data object.
- All objects that have one or more of its property values written to the JSON string are represented as an object using curly braces, including array objects and map objects. Since square brackets are the typical indicator that a substring is representing an array object, a parser will interpret the substring as an object with a property that is an array, rather than just an array. (Keep an eye out for my updated JSON parser to pair with
StringifyAll
). - A parser would need to handle read-only properties in some way.
- Some properties don't necessarily need to be parsed. For example, if I stringified an array object including its native properties, a parser setting the
Length
property would be redundant.
The above considerations are mitigated by keeping separate configurations for separate purposes. For example, keep one configuration to use when intending to later parse the string back into AHK data, and keep another configuration to use when intending to visually inspect the string.
There are some conditions which will cause StringifyAll
to skip stringifying an object. When this occurs, StringifyAll
prints a placeholder string instead. The conditions are:
- The object is a
ComObject
orComValue
. - The maximum depth is reached.
- Your callback function returned a value directing
StringifyAll
to skip the object.
When StringifyAll
encounters an object multiple times, it may skip the object and print a string representation of the object path at which the object was first encountered. Using the object path instead of the standard placeholder is so one's code or one's self can identify the correct object that was at that location when StringifyAll
was processing. This will occur when one or both of the following are true:
Options.Multiple
is false (the default is false).- Processing the object will result in infinite recursion.
StringifyAll
will require more setup to be useful compared to other stringify functions, because we usually don't need information about every property. StringifyAll
is not intended to be a replacement for other stringify functions. Where StringifyAll
shines is in cases where we need a way to programmatically define specifically what properties we want represented in the JSON string and what we want to exclude; at the cost of requiring greater setup time investment, we receive in exchange the potential to fine-tune precisely what will be present in the JSON string.
- {*} Obj - The object to stringify.
- {Object|String} [Options] -
- If you are using
ConfigLibrary
, the name of the configuration as string. See the explanation within the file "templates\ConfigLibrary.ahk". - When not using
ConfigLibrary
, the options object with zero or more options as property : value pairs.
- If you are using
- {VarRef} [OutStr] - A variable that will receive the JSON string. The string is also returned as a return value, but for very long strings, or for loops that process thousands of objects, it will be slightly faster to use the `OutStr` variable since the JSON string would not need to be copied.
- {Boolean} [SkipOptions = false] - If true,
StringifyAll.Options.Call
is not called. The purpose of this options is to enable the caller to avoid the overhead cost of processing the input options for repeated calls. Note thatOptions
must be set with an object that has been returned fromStringifyAll.Options.Call
or must be set with an object that inherits fromStringifyAll.Options.Default
. See the documentation section New in v1.3.1 for more information.
{String} - The JSON string.
The format for these options are:
{Value type} [ Option name = Default value
]
Description
CallbackError
CallbackGeneral
CallbackPlaceholder
CondenseCharLimit
CondenseCharLimitEnum1
CondenseCharLimitEnum2
CondenseCharLimitEnum2Item
CondenseCharLimitProps
CondenseDepthThreshold
CondenseDepthThresholdEnum1
CondenseDepthThresholdEnum2
CondenseDepthThresholdEnum2Item
CondenseDepthThresholdProps
EnumTypeMap
ExcludeMethods
ExcludeProps
FilterTypeMap
Indent
InitialIndent
InitialPtrListCapacity
InitialStrCapacity
CorrectFloatingPoint
ItemProp
MaxDepth
Multple
Newline
NewlineDepthLimit
PrintErrors
PropsTypeMap
QuoteNumericKeys
RootName
Singleline
StopAtTypeMap
UnsetArrayItem
Previously, StringifyAll.Options.Call
would change the base of the input Options
and of StringifyAllConfig
to facilitate inheriting the defaults. This behavior has been changed. StringifyAll.Options.Call
now copies the options onto a new object which is then used as the options object for that function call. This opens the opportunity for external code to define its own system of inheriting options while still enabling the usage of the StringifyAllConfig
class. Old code which uses StringifyAll
does not need to change anything. New code which uses StringifyAll
can define options the same as before, but new code now may define options on any of the options object's base objects. For example, this used to not be possible:
MyDefaultOptions := {
Newline: "`r`n"
, QuoteNumericKeys: true
}
Options := {
Indent: "`s`s"
}
ObjSetBase(Options, MyDefaultOptions)
StringifyAll(SomeObj, Options)
Before 1.3.1, when StringifyAll
was called neither the "Newline" nor "QuoteNumericKeys" options would have been used because the base of Options
would have been changed. Now, they both get used.
StringifyAllConfig
is optional; it does not need to exist, same as before.
When StringifyAll.Options.Call
processes the options, it copies the input (input options object) and config (StringifyAllConfig
class) options onto a new object. If an option is not defined on either object, then it copies the default value onto the new object. When StringifyAll.Options.Call
copies the default value, it creates a deep clone of the default value. This is to ensure that the built-in default does not get altered inadvertently. The same is not true for input or config values; the value is always copied directly onto the new object.
Given that this is a more costly process than the original approach, your code can call StringifyAll.Options.Call
with its options object (or no object) to get a fully processed options object, then pass that to StringifyAll.Call
while passing true
to the fourth parameter "SkipOptions". This would be slightly more efficient for repeated calls.
For even greater efficiency, you could even use the old approach in your external code. Simply define the base of your options as StringifyAll.Options.Default
and pass true
to the fourth parameter, and that is sufficient.
- {*} [ EnumTypeMap =
1
: DirectsStringifyAll
to call the object's enumerator in 1-param mode.2
: DirectsStringifyAll
to call the object's enumerator in 2-param mode.0
: DirectsStringifyAll
to not call the object's enumerator.- The
Object
being evaluated. Integer:
One of the above listed integers.- The keys are object types and the values are either
Integer
,Func
, or callableObject
as described above. - Use the
Map
'sDefault
property to set a condition for all types not included within theMap
. - If you define
Options.EnumTypeMap
as aMap
object, and ifOptions.EnumTypeMap
does not have a propertyOptions.EnumTypeMap.Default
,StringifyAll
setsOptions.EnumTypeMap.Default := 0
before processing then deletes it before returning. If an error occurs while processing that causes the thread to exit before the function returns, theOptions.EnumTypeMap.Default
property will not be deleted.
Map("Array", 1, "Map", 2, "RegExMatchInfo", 2)
]
Options.EnumTypeMap
directs StringifyAll
to call, or not to call, an object's __Enum
method. If it is called, Options.EnumTypeMap
also specifies whether it should be called in 1-param mode or 2-param mode. If the object being evaluated does not have an __Enum
method, Options.EnumTypeMap
is ignored for that object.
Options.EnumTypeMap
can be defined as a Map
object that differentiates between object types, or it can be defined with a value that is applied to objects of any type. If it is a Map
object, the keys are object type names and the values are either an Integer
, or a function that accepts the object being evaluted as its only parameter and returns an Integer
.
-
If
Options.EnumTypeMap
is an Integer
:
-
If
Options.EnumTypeMap
is a Func
or callable Object
:
Parameters
-
If
Options.EnumTypeMap
is a Map
object:
- {Boolean} [ ExcludeMethods =
true
]
-
If true, properties with a
Call
accessor and properties with only a Set
accessor are excluded from stringification.If false or unset, those kinds of properties are included in the JSON string with the name of the function object.
- {String} [ ExcludeProps =
""
]
- A comma-delimited, case-insensitive list of property names to exclude from stringification.
- {*} [ FilterTypeMap =
- The
Object
being evaluated. - A
PropsInfo.FilterGroup
object to apply a filter, or zero or an empty string to not apply a filter. - The keys are object types and the values are either
PropsInfo.FilterGroup
,Func
, or callableObject
as described above. - Use the
Map
'sDefault
property to set a condition for all types not included within theMap
. - If you define
Options.FilterTypeMap
as aMap
object, and ifOptions.FilterTypeMap
does not have a propertyOptions.FilterTypeMap.Default
,StringifyAll
setsOptions.FilterTypeMap.Default := 0
before processing then deletes it before returning. If an error occurs while processing that causes the thread to exit before the function returns, theOptions.FilterTypeMap.Default
property will not be deleted. - The term "filter" here refers to a collection of
PropsInfo.Filter
objects which comprise aPropsInfo.FilterGroup
object. Just think of a "filter" as a collection of functions thatStringifyAll
processes to exclude properties from stringification. - Filters are functions that return a nonzero value to direct
PropsInfo.Prototype.FilterActivate
to exclude a property from being exposed by thePropsInfo
object. Within the context ofStringifyAll
, this effectively causesStringifyAll
to skip the property completely; the property's value does not get evaluated. - Although filters are applied on a per-object basis,
StringifyAll
must categorize objects by their type, and soStringifyAll
uses the same filter group for all objects of the indicated type. The significance this has for you is that you can include code that responds to characteristics or conditions about individual objects. An example of this is within the "example\example.ahk" file in section I.D. "Enum options -FilterTypeMap
". The function conditionally excludes theMark
property only if the property does not have a significant value. - Exclude properties by name: To exclude properties by name, simply add a comma-delimited list of property names to the filter.
""
]
Options.FilterTypeMap
directs StringifyAll
to apply, or not to apply, a filter to the PropsInfo
objects used when processing an object's properties.
Options.FilterTypeMap
can be defined as a Map
object that differentiates between object types, or it can be defined with a value that is applied to objects of any type. If it is a Map
object, the keys are object type names and the values are either a PropsInfo.FilterGroup object, or a function that accepts the object being evaluted as its only parameter and returns a PropsInfo.FilterGroup
object.
-
If
Options.FilterTypeMap
is a Func
or callable Object
:
Parameters
-
If
Options.FilterTypeMap
is a Map
object:
-
For usage examples you can review the "inheritance\example-Inheritance.ahk" walkthrough which demonstrates using filters in the context of the
PropsInfo
class, which is the same as how they are applied by StringifyAll
.
The following is a brief explanation of how to use filters:
-
Built-in filters:
-
There are five built-in filters, four of which can be added by simply adding the index to the filter.
filter := PropsInfo.FilterGroup('__New,__Init,__Delete,Length,Capacity') filterTypeMap := Map('Array', filter)
Alt
property, i.e. exclude all properties that have multiple owners.Alt
property, i.e. exclude all properties that have only one owner.
Example adding a filter by index:
; Assume we are continuing with the filter created above. filter.Add(1)Custom filters:
- You can define a filter with any function or callable object. The function must accept the
PropsInfoItem
object as its only parameter, and should return a nonzero value if the property should be skipped for the object. Understand that the filter gets called once for every property for every object of the indicated type (unless a property gets excluded by a filter before it). The filter function isn't evaluating the object, it's evaluating thePropsInfoItem
objects associated with the object's properties. Options.FilterTypeMap
is the only option that allows us to choose what properties are included at an individual-object level.- Example using a function and applying it to the
MapObj.Default
:
MyFilterFunc(InfoItem) { switch InfoItem.Kind { case 'Get', 'Get_Set': if InfoItem.GetValue(&Value) { return 1 ; Skip properties that fail to return a value } else if !IsNumber(Value) { return 1 ; Skip properties that have a non-numeric value } default: return 1 ; Skip all other properties } } filter := PropsInfo.FilterGroup(MyFilterFunc) FilterTypeMap := Map() FilterTypeMap.Default := filter
- {Integer} [ MaxDepth =
0
]
- The maximum depth
StringifyAll
will recurse into. The root depth is 1. Note "Depth" and "indent level" do not necessarily line up. At any given point, the indentation level can be as large as 3x the depth level. This is due to how StringifyAll
handles map and array items.- {Boolean} [ Multiple =
false
]
- When true, there is no limit to how many times
StringifyAll
will process an object. Each time an individual object is encountered, it will be processed unless doing so will result in infinite recursion. When false, StringifyAll
processes each individual object a maximum of 1 time, and all other encounters result in StringifyAll
printing a placeholder string that is a string representation of the object path at which the object was first encountered.- {*} [ PropsTypeMap =
1
: DirectsStringifyAll
to process the properties.0
: DirectsStringifyAll
to skip the properties.- The
Object
being evaluated. Integer:
One of the above listed integers.- The keys are object types and the values are either
Integer
,Func
, or callableObject
as described above. - Use the
Map
'sDefault
property to set a condition for all types not included within theMap
. - If you define
Options.PropsTypeMap
as aMap
object, and ifOptions.PropsTypeMap
does not have a propertyOptions.PropsTypeMap.Default
,StringifyAll
setsOptions.PropsTypeMap.Default := 0
before processing then deletes it before returning. If an error occurs while processing that causes the thread to exit before the function returns, theOptions.PropsTypeMap.Default
property will not be deleted.
1
]
Options.PropsTypeMap
directs StringifyAll
iterate an object's properties and include their values in the JSON string.
Options.PropsTypeMap
can be defined as a Map
object that differentiates between object types, or it can be defined with a value that is applied to objects of any type. If it is a Map
object, the keys are object type names and the values are either an Integer
, or a function that accepts the object being evaluted as its only parameter and returns an Integer
.
-
If
Options.PropsTypeMap
is an Integer
:
-
If
Options.PropsTypeMap
is a Func
or callable Object
:
Parameters
-
If
Options.PropsTypeMap
is a Map
object:
- {*} [ StopAtTypeMap =
- The value is pass to the
StopAt
parameter ofGetPropsInfo
- The
Object
being evaluated. - An
Integer
orString
. - The keys are object types and the values are either
Integer
,String
,Func
, or callableObject
as described above. - Use the
Map
'sDefault
property to set a condition for all types not included within theMap
. - If you define
Options.StopAtTypeMap
as aMap
object, and ifOptions.StopAtTypeMap
does not have a propertyOptions.StopAtTypeMap.Default
,StringifyAll
setsOptions.StopAtTypeMap.Default := "-Object"
before processing then deletes it before returning. If an error occurs while processing that causes the thread to exit before the function returns, theOptions.StopAtTypeMap.Default
property will not be deleted.
"-Object"
]
Options.StopAtTypeMap
defines the value that is passed to the StopAt parameter of GetPropsInfo.
Options.StopAtTypeMap
can be defined as a Map
object that differentiates between object types, or it can be defined with a value that is applied to objects of any type. If it is a Map
object, the keys are object type names and the values are either an Integer
, String
, or a function that accepts the object being evaluted as its only parameter and returns an Integer
or String
.
-
If
Options.StopAtTypeMap
is an Integer
or String
:
-
If
Options.StopAtTypeMap
is a Func
or callable Object
:
Parameters
-
If
Options.StopAtTypeMap
is a Map
object:
- {*} [ CallbackError =
- {StringifyAll.Path} - An object with properties
Name
andPath
. See the section StringifyAll.Path. Also see the example in section CallbackPlaceholder. - {Error} - The error object.
- {*} - The object currently being evaluated.
- {PropsInfoItem} - The
PropsInfoItem
object associated with the property that caused the error. - String: The string will be printed as the property's value.
- -1:
StringifyAll
skips property completely and it is not represented in the JSON string. - Any other nonzero value:
StringifyAll
prints just theMessage
property of theError
object. - Zero or an empty string:
StringifyAll
treats theError
object as the property's value. If no other conditions prevents it, theError
object will be stringified. - Don't forget to escape the necessary characters. You can call
StringifyAll.StrEscapeJson
to do this. - Note that
StringifyAll
does not enclose the value in quotes when adding it to the JSON string. Your function should add the quote characters, or callStringifyAll.StrEscapeJson
which has the option to add the quote characters for you.
""
]
- A Func or callable Object that will be called when
StringifyAll
encounters an error attempting to access a property's value. When CallbackError
is set, StringifyAll
ignores PrintErrors
.- Parameters
- Return
- If the function returns a string:
- {*} [ CallbackGeneral =
- {StringifyAll.Path} - An object with properties
Name
andPath
. See the section StringifyAll.Path. Also see the example in section CallbackPlaceholder. - {*} - The object being evaluated.
- {VarRef} - A variable that will receive a reference to the JSON string being created.
- {String} - An optional parameter that will receive the name of the property for objects that are encountered while iterating the parent object's properties.
- {String|Integer} - An optional parameter that will receive either of:
- The loop index integer value for objects that are encountered while enumerating an object in 1-parameter mode.
- The "key" (the value received by the first variable in a for-loop) for objects that are encountered while enumerating an object in 2-parameter mode.
- String: The string will be used as the placeholder for the object in the JSON string.
- -1:
StringifyAll
skips that object completely and it is not represented in the JSON string. - Any other nonzero value:
- If
CallbackPlaceholder
is set,CallbackPlaceholder
will be called to generate the placeholder. - If
CallbackPlaceholder
is unset, the built-in placeholder is used. - Zero or an empty string:
StringifyAll
proceeds calling the next function if there is one, or proceeds stringifying the object. - Don't forget to escape the necessary characters. You can call
StringifyAll.StrEscapeJson
to do this. - Note that
StringifyAll
does not enclose the value in quotes when adding it to the JSON string. Your function should add the quote characters, or callStringifyAll.StrEscapeJson
which has the option to add the quote characters for you.
""
]
- A Func or callable Object, or an array of one or more Func or callable Object values, that will be called for each object prior to processing.
- Parameters
- Return
- The function(s) can return a nonzero value to direct
StringifyAll
to skip processing the object. Any further functions in an array of functions are necessarily also skipped in this case. The function should return a value to one of these effects:- If the function returns a string:
- {*} [ CallbackPlaceholder =
-
{StringifyAll.Path} - An object with properties
Name
andPath
. See the section StringifyAll.Path. In the below example, if your function is called for a placeholder for the object atobj.nestedObj.doubleNestedObj
, the path will be "$.nestedObj.doubleNestedObj".Obj := { nestedObj: { doubleNestedObj: { prop: 'value' } } }
- {*} - The object being evaluated.
- {VarRef} - An optional
VarRef
parameter that will receive the name of the property for objects that are encountered while iterating the parent object's properties. - {VarRef} - An optional
VarRef
parameter that will receive either of: - The loop index integer value for objects that are encountered while enumerating an object in 1-parameter mode.
- The "key" (the value received by the first variable in a for-loop) for objects that are encountered while enumerating an object in 2-parameter mode.
- String: The placeholder string.
- Don't forget to escape the necessary characters. You can call
StringifyAll.StrEscapeJson
to do this. - Note that
StringifyAll
does not enclose the value in quotes when adding it to the JSON string. Your function should add the quote characters, or callStringifyAll.StrEscapeJson
which has the option to add the quote characters for you.
""
]
- When
StringifyAll
skips processing an object, a placeholder is printed instead. You can define CallbackPlaceholder
with any callable object to customize the string that gets printed.- Parameters
- It does not matter if the function modifies the two
VarRef
parameters as StringifyAll
will not use them again at that point.- Return
-
Each of
CondenseCharLimit
, CondenseCharLimitEnum1
, CondenseCharLimitEnum2
, CondenseCharLimitEnum2Item
, and CondenseCharLimitProps
set a threshold which StringifyAll
will use to condense an object's substring if the length, in characters, of the substring is less than or equal to the value. The substring length is measured beginning from the open brace and excludes external whitespace such as newline characters and indentation that are not part of a string literal value.
If any of the
Options.CondenseCharLimit
options are in use, the Options.CondenseDepthThreshold
options set a depth requirement to apply the option. For example, if Options.CondenseDepthThreshold == 2
, all Options.CondenseCharLimit
options will only be applied if the current depth is 2 or more; values at the root depth (1) will be processed without applying the Options.CondenseCharLimit
option.
- {Integer} [ CondenseCharLimit =
0
]
- Applies to all substrings. If
Options.CondenseCharLimit
is set, you can still specify individual options for the others and the individual option will take precedence over CondenseCharLimit
.- {Integer} [ CondenseCharLimitEnum1 =
0
]
- Applies to substrings that are created by calling an object's enumerator in 1-param mode.
- {Integer} [ CondenseCharLimitEnum2 =
0
]
- Applies to substrings that are created by calling an object's enumerator in 2-param mode.
- {Integer} [ CondenseCharLimitEnum2Item =
0
]
- Applies to substrings that are created for each key-value pair when iterating an object's enumerator in 2-param mode. (Added in 1.1.5)
- {Integer} [ CondenseCharLimitProps =
0
]
- Applies to substrings that are created by processing an object's properties.
- {Integer} [ CondenseDepthThreshold =
0
]
- Applies to all substrings. If
Options.CondenseDepthThreshold
is set, you can still specify individual options for the others and the individual option will take precedence over Options.CondenseDepthThreshold
. (Added in 1.2.0)- {Integer} [ CondenseDepthThresholdEnum1 =
0
]
- Applies to substrings that are created by calling an object's enumerator in 1-param mode. (Added in 1.2.0)
- {Integer} [ CondenseDepthThresholdEnum2 =
0
]
- Applies to substrings that are created by calling an object's enumerator in 2-param mode. (Added in 1.2.0)
- {Integer} [ CondenseDepthThresholdEnum2Item =
0
]
- Applies to substrings that are created for each key-value pair when iterating an object's enumerator in 2-param mode. (Added in 1.2.0)
- {Integer} [ CondenseDepthThresholdProps =
0
]
- Applies to substrings that are created by processing an object's properties. (Added in 1.2.0)
- {String} [ Indent =
"`s`s`s`s"
]
- The literal string that will be used for one level of indentation.
- {String} [ InitialIndent =
0
]
- The initial indent level. Note that the first line with the opening brace is not indented. This is to make it easier to use the output from one `StringifyAll` call as a property value in a separate JSON string.
obj1 := { prop1: { prop2: 'val2' } }
obj2 := { prop3: { prop4: 'val3' } }
; To exclude the `Object` inherited properties.
filter := PropsInfo.FilterGroup(1)
FilterTypeMap := Map('Object', filter)
json := StringifyAll(obj1, { FilterTypeMap: FilterTypeMap })
json := StrReplace(json, '"val2"', StringifyAll(obj2, { InitialIndent: 2, FilterTypeMap: FilterTypeMap }))
OutputDebug(A_Clipboard := json)
{
"prop1": {
"prop2": {
"prop3": {
"prop4": "val3"
}
}
}
}
- {String} [ Newline =
"`r`n"
]
- The literal string that will be used for line breaks. If set to zero or an empty string, the
Singleline
option is effectively enabled and StringifyAll
disables Options.Indent
for you. If you have a need to direct StringifyAll
to not use newline characters but still use indentation where it typically would, you should set Options.Newline
with a zero-width character like 0xFEFF.- {Integer} [ NewlineDepthLimit =
0
]
- Sets a threshold directing
StringifyAll
to stop adding line breaks between values after exceeding the threshold.- {Boolean} [ Singleline =
false
]
- If true, the JSON string is printed without line breaks or indentation. All other "Newline and indent options" are ignored.
- {Number|String} [ CorrectFloatingPoint =
false
]
-
If nonzero,
StringifyAll
will round numbers that appear to be effected by the floating point
precision issue described in AHK's documentation.
This process is facilitated by a regex pattern that attempts to identify these occurrences.
If Options.CorrectFloatingPoint
is a nonzero number, StringifyAll
will use the built-in
default pattern "S)(?<round>(?:0{3,}|9{3,})\d)$"
. You can also set
Options.CorrectFloatingPoint
with your own regex pattern as a string and
StringifyAll
will use that pattern.Default pattern:
"S)(?<round>(?:0{3,}|9{3,})\d)$"
The pattern requires that a string ends in a sequence of three or more zeroes followed by any number, or a sequence of three or more nines followed by any number. The string is then passed to
Round
and
rounded to the character before the beginning of the match.Using your own pattern:
The following is the literal code that facilitates this option.
Val
is the number being
evaluated.if flag_quote_number { if InStr(Val, '.') && RegExMatch(Val, pattern_correctFloatingPoint, &matchNum) { Val := '"' Round(Val, StrLen(Val) - InStr(Val, '.') - matchNum.Len['round']) '"' } else { Val := '"' Val '"' } } else { if InStr(Val, '.') && RegExMatch(Val, pattern_correctFloatingPoint, &matchNum) { Val := Round(Val, StrLen(Val) - InStr(Val, '.') - matchNum.Len['round']) } else { Val := Val } }I added the "round" subcapture group to make it easier to use complex logic; the default pattern would not actually require any subcapture group. If using your own pattern,
StringifyAll
will
substract the length of the "round" subcapture group from the number of characters that follow the
decimal point.If
Options.CorrectFloatingPoint
is zero or an empty string, no correction occurs.
- {String} [ ItemProp =
"__Items__"
]
- The name that
StringifyAll
will use as a faux-property for including an object's items returned by its enumerator.- {Boolean|String} [ PrintErrors =
- If
PrintErrors
is a string value, it should be a comma-delimited list ofError
property names to include in the output as the value of the property that caused the error. - If any other nonzero value,
StringifyAll
will print just the "Message" property of theError
object in the string. - If zero or an empty string,
StringifyAll
skips the property.
false
]
-
Influences how
StringifyAll
handles errors when accessing a property value. PrintErrors
is ignored if CallbackError
is set.- {Boolean} [ QuoteNumericKeys =
false
]
- When true, and when
StringifyAll
is processing an object's enumerator in 2-param mode, if the value returned to the first parameter (the "key") is numeric, it will be quoted in the JSON string.- {String} [ RootName =
"$"
]
- Prior to recursively stringifying a nested object,
StringifyAll
checks if the object has already been processed. If an object has already been processed, and if Options.Multiple
is false or if processing the object will result in infinite recursion, a placeholder is printed in its place. The placeholder printed as a result of this condition is different than placeholders printed for other reasons. In this case, the placeholder is a string representation of the object path at which the object was first encountered. This is so one's self, or one's code, can locate the object in the JSON string if needed. RootName
specifies the name of the root object used within any occurrences of this placeholder string.- {String} [ UnsetArrayItem =
"`"`""
]
- The string to print for unset array items.
- {Integer} [ InitialPtrListCapacity =
64
]
StringifyAll
tracks the ptr addresses of every object it stringifies to prevent infinite recursion. StringifyAll
will set the initial capacity of the Map
object used for this purpose to InitialPtrListCapacity
.- {Integer} [ InitialStrCapacity =
65536
]
StringifyAll
calls VarSetStrCapacity
using InitialStrCapacity
for the output string during the initialization stage. For the best performance, you can overestimate the approximate length of the string; StringifyAll
calls VarSetStrCapacity(&OutStr, -1)
at the end of the function to release any unused memory.Added 1.2.0.
StringifyAll.Path
is a solution for tracking an object path as a string value. Callback functions will receive an instance of StringifyAll.Path
to the first parameter.
- Call: Returns the object path applying AHK escape sequences with a backtick where appropriate.
- Unescaped: Returns the object path without applying escape sequences.
- Name:
- If the object associated with the
StringifyAll.Path
object was encountered when enumerating its parent object in 1-param mode, anInteger
representing the index of the associated object. - If the object associated with the
StringifyAll.Path
object was encountered when enumerating its parent object in 2-param mode, aString
representing the "key" (value set to the first parameter in thefor
loop) of the associated object. - If the object associated with the
StringifyAll.Path
object was encountered when iterating its parent object's properties, aString
representing the property's name.
- If the object associated with the
- Path: A
String
representing the object path of the object associate with theStringifyAll.Path
object, including the current object'sName
as described above.
This section describes StringifyAll
's process. This section is intended to help you better understand how the options will impact the output string. This section is not complete.
This section needs updated.
The following is a description of the part of the process which the function(s) are called.
- If the value has already been stringified, processes the object according to Multple.
- If the value is a
ComObject
orComValue
, the value is skipped. - If
MaxDepth
has been reached, the value is skipped.
StringifyAll
proceeds in two stages, initialization and recursive processing. After initialization, the function Recurse
is called once, which starts the second stage.
When
StringifyAll
encounters a value that is an object, it proceeds through a series of condition checks to determine if it will call Recurse
again for that value. When a value is skipped, a placeholder is printed instead.StringifyAll
checks the following conditions.
StringifyAll
to skip the object, StringifyAll
then calls the CallbackGeneral
function(s).
If none of the
CallbackGeneral
functions direct StringifyAll
to skip the object, Recurse
is called.
This section needs updated.
- Added
Options.CorrectFloatingPoint
. - Adjusted
StringifyAll.Options
. See section New in v1.3.1. - Added parameter "SkipOptions". See section New in v1.3.1.
- Added
StringifyAll.GetPlaceholderSubstrings
. - Fixed: After 1.2.0, if
Options.FilterTypeMap
was set with aPropsInfo.FilterGroup
object,StringifyAll
erroneously treated the value as aMap
object. This has been corrected. - Fixed: After 1.2.0, map keys had a change to not be escaped properly. This is corrected.
- Adjusted how
StringifyAll
handles the "key" values (the value assigned to the first parameter of a 2-paramfor
loop). The value is no longer escaped prior to callingOptions.CallbackPlaceholder
orOptions.CallbackGeneral
. - Adjusted
StringifyAll.Path
. It now caches the path value, and the process for constructing the path string has been optimized. Item names that are strings are quoted with single quote characters, and internal single quote characters are always escaped with a backtick.
- Added
StringifyAll.Path
. - Added
Options.CondenseDepthThreshold
,Options.CondenseDepthThresholdEnum1
,Options.CondenseDepthThresholdEnum2
,Options.CondenseDepthThresholdEnum2Item
, andOptions.CondenseDepthThresholdProps
. - Removed
StringifyAll.__New
as it is no longer needed. - Removed some documentation in the parameter hint for
StringifyAll.Call
. - Fixed two errors in "example\example.ahk".
- Fixed
Options.CallbackGeneral
not receiving thecontroller
(nowStringify.Path
) object to the first parameter as described in the documentation. - Adjusted the parameters passed to the callback functions. The
Controller
object is no longer passed to callback functions. Instead, aStringifyAll.Path
object is passed to the parameters that used to receive theController
object. In this documentation an instance ofStringifyAll.Path
is referred to asPathObj
.StringifyAll.Path
is a solution for tracking object paths using string values. Accessing thePathObj.Path
property returns the object path, so this change is backward-compatible (unless external code made use of any of the methods that are available on theController
object, which will no longer be available). See the documentation section "StringifyAll.Path" for further details. - Adjusted the handling of all of the "TypeMap" options. If any of these options are defined with a value that does not inherit from
Map
, that value is used for all types. If any of these options are defined with an object that inherits fromMap
and that object has a property "Count" with a value of0
,StringifyAll
optimizes the handling of the option by creating a reference to the "Default" value and using that for all types. - Adjusted
Recurse
.HasMethod(Obj, "__Enum")
is checked prior to callingCheckEnum
. - Optimized handling of various options.
- Fixed
StringifyAll.StrUnescapeJson
. - Added "test\test-StrUnescapeJson.ahk".
- Implemented
Options.InitialIndent
.
- Improved the handling of the "CondenseCharLimit" options.
- Implemented
Options.CondenseCharLimitEnum2Item
.
- Removed duplicate line of code.
- When
StringifyAll
processes an object, it caches the string object path. Previously, the cached path was overwritten each time an object was processed, resulting in a possibility forStringifyAll
to cause AHK to crash if it entered into an infinite loop. This has been corrected by adjusted the tracking of object ptr addresses to add the string object path to an array each time an object is processed, and to check all paths when testing if two objects share a parent-child relationship.
- Added error for invalid return values from
Options.EnumTypeMap
.
- Fixed: If an object's enumerator is called in 1-param mode but returns zero valid items, the empty object no longer has a line break between the open and close bracket.
- Breaking: Increased the number of values passed to
CallbackGeneral
. - Implemented
Options.CallbackError
. - Implemented
Options.Multiple
. - Created "test\test-errors.ahk" to test the error-related options.
- Created "test\test-recursion.ahk" to test
Options.Multiple
. - Created "test\test.ahk" to run all tests.
- Adjusted
Options.PrintErrors
to allow specifying what properties to be included in the output string. - Fixed an error causing a small chance for
StringifyAll
to incorrectly apply a property value to the subsequent property. - Fixed an error that occurred when using
Options.CallbackGeneral
andStringifyAll
encounters a duplicate object resulting in an invalid JSON string.
- Fixed an error causing
StringifyAll
to incorrectly handle objects returned by aMap
object's enumerator, resulting in an invalid JSON string.
- Corrected the order of operations in
StringifyAll.StrUnescapeJson
.
- Implemented
ConfigLibrary
.
- Adjusted how
Options.PropsTypeMap
is handled. This change did not modifyStringifyAll
's behavior, but it is now more clear both in the code and in the documentation what the default value is and what the default value does. - Added "StringifyAll's process" to the docs.