Skip to content

A customizable solution for serializing AutoHotkey (AHK) objects - including inherited properties - into 100% valid JSON strings.

License

Notifications You must be signed in to change notification settings

Nich-Cebolla/StringifyAll

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

83 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

StringifyAll - v1.3.1

A customizable solution for serializing AutoHotkey (AHK) object properties, including inherited properties, and/or items into a 100% valid JSON string.

AutoHotkey forum post

https://www.autohotkey.com/boards/viewtopic.php?f=83&t=137415&p=604407

Table of contents

  1. Introduction
  2. Parameters
  3. Returns
  4. Options
    1. Jump to category
    2. New in v1.3.1
    3. Enum options
    4. Callbacks
    5. Newline and indent options
    6. Print options
    7. General options
  5. StringifyAll.Path
  6. StringifyAll's process
    1. Properties
    2. CallbackGeneral
    3. Calling the enumerator
  7. Changelog

Introduction

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 or ComValue.
  • 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.

Parameters

  1. {*} Obj - The object to stringify.
  2. {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.
  3. {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.
  4. {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 that Options must be set with an object that has been returned from StringifyAll.Options.Call or must be set with an object that inherits from StringifyAll.Options.Default. See the documentation section New in v1.3.1 for more information.

Returns

{String} - The JSON string.

Options

The format for these options are:
{Value type} [ Option name = Default value ]
Description

Jump to category


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

New in v1.3.1

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.

Enum options

    {*} [ EnumTypeMap = 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:
      • 1: Directs StringifyAll to call the object's enumerator in 1-param mode.
      • 2: Directs StringifyAll to call the object's enumerator in 2-param mode.
      • 0: Directs StringifyAll to not call the object's enumerator.
      If Options.EnumTypeMap is a Func or callable Object:
      Parameters
      1. The Object being evaluated.
      Return
      • Integer: One of the above listed integers.
      If Options.EnumTypeMap is a Map object:
      • The keys are object types and the values are either Integer, Func, or callable Object as described above.
      • Use the Map's Default property to set a condition for all types not included within the Map.
      • If you define Options.EnumTypeMap as a Map object, and if Options.EnumTypeMap does not have a property Options.EnumTypeMap.Default, StringifyAll sets Options.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, the Options.EnumTypeMap.Default property will not be deleted.
    {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 = "" ]
      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
      1. The Object being evaluated.
      Return
      • A PropsInfo.FilterGroup object to apply a filter, or zero or an empty string to not apply a filter.
      If Options.FilterTypeMap is a Map object:
      • The keys are object types and the values are either PropsInfo.FilterGroup, Func, or callable Object as described above.
      • Use the Map's Default property to set a condition for all types not included within the Map.
      • If you define Options.FilterTypeMap as a Map object, and if Options.FilterTypeMap does not have a property Options.FilterTypeMap.Default, StringifyAll sets Options.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, the Options.FilterTypeMap.Default property will not be deleted.
      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:
      • The term "filter" here refers to a collection of PropsInfo.Filter objects which comprise a PropsInfo.FilterGroup object. Just think of a "filter" as a collection of functions that StringifyAll 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 the PropsInfo object. Within the context of StringifyAll, this effectively causes StringifyAll 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 so StringifyAll 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 the Mark property only if the property does not have a significant value.
        Built-in filters:
          There are five built-in filters, four of which can be added by simply adding the index to the filter.
        • Exclude properties by name: To exclude properties by name, simply add a comma-delimited list of property names to the filter.
        • filter := PropsInfo.FilterGroup('__New,__Init,__Delete,Length,Capacity')
          filterTypeMap := Map('Array', filter)
          
        • 1: Exclude all items that are not own properties of the root object.
        • 2: Exclude all items that are own properties of the root object.
        • 3: Exclude all items that have an Alt property, i.e. exclude all properties that have multiple owners.
        • 4: Exclude all items that do not have an 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 the PropsInfoItem 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 ]
      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:
      • 1: Directs StringifyAll to process the properties.
      • 0: Directs StringifyAll to skip the properties.
      If Options.PropsTypeMap is a Func or callable Object:
      Parameters
      1. The Object being evaluated.
      Return
      • Integer: One of the above listed integers.
      If Options.PropsTypeMap is a Map object:
      • The keys are object types and the values are either Integer, Func, or callable Object as described above.
      • Use the Map's Default property to set a condition for all types not included within the Map.
      • If you define Options.PropsTypeMap as a Map object, and if Options.PropsTypeMap does not have a property Options.PropsTypeMap.Default, StringifyAll sets Options.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, the Options.PropsTypeMap.Default property will not be deleted.
    {*} [ StopAtTypeMap = "-Object" ]
      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:
      • The value is pass to the StopAt parameter of GetPropsInfo
      If Options.StopAtTypeMap is a Func or callable Object:
      Parameters
      1. The Object being evaluated.
      Return
      • An Integer or String.
      If Options.StopAtTypeMap is a Map object:
      • The keys are object types and the values are either Integer, String, Func, or callable Object as described above.
      • Use the Map's Default property to set a condition for all types not included within the Map.
      • If you define Options.StopAtTypeMap as a Map object, and if Options.StopAtTypeMap does not have a property Options.StopAtTypeMap.Default, StringifyAll sets Options.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, the Options.StopAtTypeMap.Default property will not be deleted.

Callbacks

    {*} [ CallbackError = "" ]
      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
      1. {StringifyAll.Path} - An object with properties Name and Path. See the section StringifyAll.Path. Also see the example in section CallbackPlaceholder.
      2. {Error} - The error object.
      3. {*} - The object currently being evaluated.
      4. {PropsInfoItem} - The PropsInfoItem object associated with the property that caused the error.
      Return
        • 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 the Message property of the Error object.
        • Zero or an empty string: StringifyAll treats the Error object as the property's value. If no other conditions prevents it, the Error object will be stringified.
        If the function returns a string:
      • Don't forget to escape the necessary characters. You can call StringifyAll.StrEscapeJsonto 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 call StringifyAll.StrEscapeJson which has the option to add the quote characters for you.
      {*} [ CallbackGeneral = "" ]
        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
        1. {StringifyAll.Path} - An object with properties Name and Path. See the section StringifyAll.Path. Also see the example in section CallbackPlaceholder.
        2. {*} - The object being evaluated.
        3. {VarRef} - A variable that will receive a reference to the JSON string being created.
        4. {String} - An optional parameter that will receive the name of the property for objects that are encountered while iterating the parent object's properties.
        5. {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.
        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:
        • 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.
        If the function returns a string:
      • Don't forget to escape the necessary characters. You can call StringifyAll.StrEscapeJsonto 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 call StringifyAll.StrEscapeJson which has the option to add the quote characters for you.
      {*} [ CallbackPlaceholder = "" ]
        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.
        1. {StringifyAll.Path} - An object with properties Name and Path. See the section StringifyAll.Path. In the below example, if your function is called for a placeholder for the object at obj.nestedObj.doubleNestedObj, the path will be "$.nestedObj.doubleNestedObj".
          Obj := {
              nestedObj: {
                  doubleNestedObj: { prop: 'value' }
              }
          }
        2. {*} - The object being evaluated.
        3. {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.
        4. {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.
        Return
        • String: The placeholder string.
          • Don't forget to escape the necessary characters. You can call StringifyAll.StrEscapeJsonto 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 call StringifyAll.StrEscapeJson which has the option to add the quote characters for you.

    Newline and indent options

      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.

    Print options

      {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 = false ]
        Influences how StringifyAll handles errors when accessing a property value. PrintErrors is ignored if CallbackError is set.
      • If PrintErrors is a string value, it should be a comma-delimited list of Error 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 the Error object in the string.
      • If zero or an empty string, StringifyAll skips the property.
      {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.

    General options

      {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.

    StringifyAll.Path

    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.

    Instance methods

    • Call: Returns the object path applying AHK escape sequences with a backtick where appropriate.
    • Unescaped: Returns the object path without applying escape sequences.

    Instance properties

    • Name:
      • If the object associated with the StringifyAll.Path object was encountered when enumerating its parent object in 1-param mode, an Integer 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, a String representing the "key" (value set to the first parameter in the for loop) of the associated object.
      • If the object associated with the StringifyAll.Path object was encountered when iterating its parent object's properties, a String representing the property's name.
    • Path: A String representing the object path of the object associate with the StringifyAll.Path object, including the current object's Name as described above.

    StringifyAll's process

    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.

    Properties

    This section needs updated.

    CallbackGeneral

    The following is a description of the part of the process which the function(s) are called.

      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.
      • If the value has already been stringified, processes the object according to Multple.
      • If the value is a ComObject or ComValue, the value is skipped.
      • If MaxDepth has been reached, the value is skipped.
      If none of the above conditions cause 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.

    Calling the enumerator

    This section needs updated.

    Changelog

    2025-09-19 - 1.3.1

    • Added Options.CorrectFloatingPoint.
    • Adjusted StringifyAll.Options. See section New in v1.3.1.
    • Added parameter "SkipOptions". See section New in v1.3.1.

    2025-07-06 - 1.3.0

    • Added StringifyAll.GetPlaceholderSubstrings.
    • Fixed: After 1.2.0, if Options.FilterTypeMap was set with a PropsInfo.FilterGroup object, StringifyAll erroneously treated the value as a Map 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-param for loop). The value is no longer escaped prior to calling Options.CallbackPlaceholder or Options.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.

    2025-07-05 - 1.2.0

    • Added StringifyAll.Path.
    • Added Options.CondenseDepthThreshold, Options.CondenseDepthThresholdEnum1, Options.CondenseDepthThresholdEnum2, Options.CondenseDepthThresholdEnum2Item, and Options.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 the controller (now Stringify.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, a StringifyAll.Path object is passed to the parameters that used to receive the Controller object. In this documentation an instance of StringifyAll.Path is referred to as PathObj. StringifyAll.Path is a solution for tracking object paths using string values. Accessing the PathObj.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 the Controller 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 from Map and that object has a property "Count" with a value of 0, 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 calling CheckEnum.
    • Optimized handling of various options.

    2025-06-28 - 1.1.7

    • Fixed StringifyAll.StrUnescapeJson.
    • Added "test\test-StrUnescapeJson.ahk".

    2025-06-19 - 1.1.6

    • Implemented Options.InitialIndent.

    2025-06-15 - 1.1.5

    • Improved the handling of the "CondenseCharLimit" options.
    • Implemented Options.CondenseCharLimitEnum2Item.

    2025-06-08 - 1.1.4

    • Removed duplicate line of code.

    2025-05-31 - 1.1.3

    • 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 for StringifyAll 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.

    2025-05-31 - 1.1.2

    • Added error for invalid return values from Options.EnumTypeMap.

    2025-05-31 - 1.1.1

    • 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.

    2025-05-31 - 1.1.0

    • 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 and StringifyAll encounters a duplicate object resulting in an invalid JSON string.

    2025-05-30 - 1.0.5

    • Fixed an error causing StringifyAll to incorrectly handle objects returned by a Map object's enumerator, resulting in an invalid JSON string.

    2025-05-29 - 1.0.4

    • Corrected the order of operations in StringifyAll.StrUnescapeJson.

    2025-05-29 - 1.0.3

    • Implemented ConfigLibrary.

    2025-05-28 - 1.0.1

    • Adjusted how Options.PropsTypeMap is handled. This change did not modify StringifyAll'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.

    About

    A customizable solution for serializing AutoHotkey (AHK) objects - including inherited properties - into 100% valid JSON strings.

    Topics

    Resources

    License

    Stars

    Watchers

    Forks

    Releases

    No releases published

    Packages

    No packages published