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
ConfigLibraryclass and reference the configuration by name. See the file "templates\ConfigLibrary.ahk". (Added 1.0.3). - Define a class
StringifyAllConfiganywhere in your code. - Pass an object to the
Optionsparameter.
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
Lengthproperty 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
ComObjectorComValue. - The maximum depth is reached.
- Your callback function returned a value directing
StringifyAllto 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.Multipleis 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.Callis 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 thatOptionsmust be set with an object that has been returned fromStringifyAll.Options.Callor 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: DirectsStringifyAllto call the object's enumerator in 1-param mode.2: DirectsStringifyAllto call the object's enumerator in 2-param mode.0: DirectsStringifyAllto not call the object's enumerator.- The
Objectbeing evaluated. Integer:One of the above listed integers.- The keys are object types and the values are either
Integer,Func, or callableObjectas described above. - Use the
Map'sDefaultproperty to set a condition for all types not included within theMap. - If you define
Options.EnumTypeMapas aMapobject, and ifOptions.EnumTypeMapdoes not have a propertyOptions.EnumTypeMap.Default,StringifyAllsetsOptions.EnumTypeMap.Default := 0before processing then deletes it before returning. If an error occurs while processing that causes the thread to exit before the function returns, theOptions.EnumTypeMap.Defaultproperty 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
Objectbeing evaluated. - A
PropsInfo.FilterGroupobject 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 callableObjectas described above. - Use the
Map'sDefaultproperty to set a condition for all types not included within theMap. - If you define
Options.FilterTypeMapas aMapobject, and ifOptions.FilterTypeMapdoes not have a propertyOptions.FilterTypeMap.Default,StringifyAllsetsOptions.FilterTypeMap.Default := 0before processing then deletes it before returning. If an error occurs while processing that causes the thread to exit before the function returns, theOptions.FilterTypeMap.Defaultproperty will not be deleted. - The term "filter" here refers to a collection of
PropsInfo.Filterobjects which comprise aPropsInfo.FilterGroupobject. Just think of a "filter" as a collection of functions thatStringifyAllprocesses to exclude properties from stringification. - Filters are functions that return a nonzero value to direct
PropsInfo.Prototype.FilterActivateto exclude a property from being exposed by thePropsInfoobject. Within the context ofStringifyAll, this effectively causesStringifyAllto skip the property completely; the property's value does not get evaluated. - Although filters are applied on a per-object basis,
StringifyAllmust categorize objects by their type, and soStringifyAlluses 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 theMarkproperty 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
PropsInfoItemobject 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 thePropsInfoItemobjects associated with the object's properties. Options.FilterTypeMapis 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: DirectsStringifyAllto process the properties.0: DirectsStringifyAllto skip the properties.- The
Objectbeing evaluated. Integer:One of the above listed integers.- The keys are object types and the values are either
Integer,Func, or callableObjectas described above. - Use the
Map'sDefaultproperty to set a condition for all types not included within theMap. - If you define
Options.PropsTypeMapas aMapobject, and ifOptions.PropsTypeMapdoes not have a propertyOptions.PropsTypeMap.Default,StringifyAllsetsOptions.PropsTypeMap.Default := 0before processing then deletes it before returning. If an error occurs while processing that causes the thread to exit before the function returns, theOptions.PropsTypeMap.Defaultproperty 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
StopAtparameter ofGetPropsInfo - The
Objectbeing evaluated. - An
IntegerorString. - The keys are object types and the values are either
Integer,String,Func, or callableObjectas described above. - Use the
Map'sDefaultproperty to set a condition for all types not included within theMap. - If you define
Options.StopAtTypeMapas aMapobject, and ifOptions.StopAtTypeMapdoes not have a propertyOptions.StopAtTypeMap.Default,StringifyAllsetsOptions.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.Defaultproperty 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
NameandPath. See the section StringifyAll.Path. Also see the example in section CallbackPlaceholder. - {Error} - The error object.
- {*} - The object currently being evaluated.
- {PropsInfoItem} - The
PropsInfoItemobject associated with the property that caused the error. - String: The string will be printed as the property's value.
- -1:
StringifyAllskips property completely and it is not represented in the JSON string. - Any other nonzero value:
StringifyAllprints just theMessageproperty of theErrorobject. - Zero or an empty string:
StringifyAlltreats theErrorobject as the property's value. If no other conditions prevents it, theErrorobject will be stringified. - Don't forget to escape the necessary characters. You can call
StringifyAll.StrEscapeJsonto do this. - Note that
StringifyAlldoes not enclose the value in quotes when adding it to the JSON string. Your function should add the quote characters, or callStringifyAll.StrEscapeJsonwhich 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
NameandPath. 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:
StringifyAllskips that object completely and it is not represented in the JSON string. - Any other nonzero value:
- If
CallbackPlaceholderis set,CallbackPlaceholderwill be called to generate the placeholder. - If
CallbackPlaceholderis unset, the built-in placeholder is used. - Zero or an empty string:
StringifyAllproceeds 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.StrEscapeJsonto do this. - Note that
StringifyAlldoes not enclose the value in quotes when adding it to the JSON string. Your function should add the quote characters, or callStringifyAll.StrEscapeJsonwhich 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
NameandPath. 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
VarRefparameter that will receive the name of the property for objects that are encountered while iterating the parent object's properties. - {VarRef} - An optional
VarRefparameter 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.StrEscapeJsonto do this. - Note that
StringifyAlldoes not enclose the value in quotes when adding it to the JSON string. Your function should add the quote characters, or callStringifyAll.StrEscapeJsonwhich 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
PrintErrorsis a string value, it should be a comma-delimited list ofErrorproperty names to include in the output as the value of the property that caused the error. - If any other nonzero value,
StringifyAllwill print just the "Message" property of theErrorobject in the string. - If zero or an empty string,
StringifyAllskips 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.Pathobject was encountered when enumerating its parent object in 1-param mode, anIntegerrepresenting the index of the associated object. - If the object associated with the
StringifyAll.Pathobject was encountered when enumerating its parent object in 2-param mode, aStringrepresenting the "key" (value set to the first parameter in theforloop) of the associated object. - If the object associated with the
StringifyAll.Pathobject was encountered when iterating its parent object's properties, aStringrepresenting the property's name.
- If the object associated with the
- Path: A
Stringrepresenting the object path of the object associate with theStringifyAll.Pathobject, including the current object'sNameas 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
ComObjectorComValue, the value is skipped. - If
MaxDepthhas 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.FilterTypeMapwas set with aPropsInfo.FilterGroupobject,StringifyAllerroneously treated the value as aMapobject. This has been corrected. - Fixed: After 1.2.0, map keys had a change to not be escaped properly. This is corrected.
- Adjusted how
StringifyAllhandles the "key" values (the value assigned to the first parameter of a 2-paramforloop). The value is no longer escaped prior to callingOptions.CallbackPlaceholderorOptions.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.__Newas it is no longer needed. - Removed some documentation in the parameter hint for
StringifyAll.Call. - Fixed two errors in "example\example.ahk".
- Fixed
Options.CallbackGeneralnot receiving thecontroller(nowStringify.Path) object to the first parameter as described in the documentation. - Adjusted the parameters passed to the callback functions. The
Controllerobject is no longer passed to callback functions. Instead, aStringifyAll.Pathobject is passed to the parameters that used to receive theControllerobject. In this documentation an instance ofStringifyAll.Pathis referred to asPathObj.StringifyAll.Pathis a solution for tracking object paths using string values. Accessing thePathObj.Pathproperty returns the object path, so this change is backward-compatible (unless external code made use of any of the methods that are available on theControllerobject, 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 fromMapand that object has a property "Count" with a value of0,StringifyAlloptimizes 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
StringifyAllprocesses an object, it caches the string object path. Previously, the cached path was overwritten each time an object was processed, resulting in a possibility forStringifyAllto 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.PrintErrorsto allow specifying what properties to be included in the output string. - Fixed an error causing a small chance for
StringifyAllto incorrectly apply a property value to the subsequent property. - Fixed an error that occurred when using
Options.CallbackGeneralandStringifyAllencounters a duplicate object resulting in an invalid JSON string.
- Fixed an error causing
StringifyAllto incorrectly handle objects returned by aMapobject's enumerator, resulting in an invalid JSON string.
- Corrected the order of operations in
StringifyAll.StrUnescapeJson.
- Implemented
ConfigLibrary.
- Adjusted how
Options.PropsTypeMapis 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.