Version 0 of Alternative JSON

Updated 2017-10-04 23:55:50 by stevel

AMG: [jsonEncode] converts from a tagged data format to JSON.

Types

The available type tags are:

array
List of elements.
object
Key-value dictionary of elements.
string
Arbitrary string value.
number
Integer and real numeric value.
literal
false, null, or true.

Array and object type composition is supported in two ways. If the type is array or object, each element is represented as a two-element list, being the type and the value. This gets quite cumbersome when the values all have uniform type, so the outer type may be a two-element list, the second of which is the type used for all values. More deeply nested data structures can be defined either by further nesting the second type element or by flattening the list.

Examples

% jsonEncode string hello
"hello"
% jsonEncode number 42
42
% jsonEncode literal null
null
% jsonEncode array {{string hello} {number 42} {literal null}}
["hello",42,null]
% jsonEncode object {foo {string hello} bar {number 42} quux {literal null}}
{"foo":"hello","bar":42,"quux":null}
% jsonEncode array {{array {{number 1} {number 2}}} {array {{number 3} {number 4}}}}
[[1,2],[3,4]]
% jsonEncode {array {array number}} {{1 2} {3 4}}
[[1,2],[3,4]]
% jsonEncode {array array number} {{1 2} {3 4}}
[[1,2],[3,4]]
% jsonEncode {array array string} {{1 2} {3 4}}
[["1","2"],["3","4"]]
% jsonEncode {object object string} {name {first Andy last Goth} contact {email [email protected] telephone 555-1234}}
{"name":{"first":"Andy","last":"Goth"},"contact":{"email":"[email protected]","telephone":"555-1234"}}

Implementation

# jsonEncode --
# Encodes data in the JSON format per https://tools.ietf.org/html/rfc7159.
proc jsonEncode {type data} {
    set comma {}
    if {[llength $type] == 1} {
        # One-element type.
        switch $type {
        array {
            # Recursively encode each array element.  Elements are themselves
            # two-element lists consisting of type and value.
            append result \[
            foreach element $data {
                append result $comma [jsonEncode {*}$element]
                set comma ,
            }
            append result \]
        } object {
            # Recursively encode each object key and value.  Keys are always
            # strings, and values are two-element lists consisting of type and
            # underlying data value.
            append result \{
            foreach {key element} $data {
                append result $comma [jsonEncode string $key] :\
                        [jsonEncode {*}$element]
                set comma ,
            }
            append result \}
        } string {
            # Encode the minimal set of required escape sequences.
            append result \" [string map {
                \x00 \\u0000    \x01 \\u0001    \x02 \\u0002    \x03 \\u0003
                \x04 \\u0004    \x05 \\u0005    \x06 \\u0006    \x07 \\u0007
                \x08 \\u0008    \x09 \\u0009    \x0a \\u000a    \x0b \\u000b
                \x0c \\u000c    \x0d \\u000d    \x0e \\u000e    \x0f \\u000f
                \x10 \\u0010    \x11 \\u0011    \x12 \\u0012    \x13 \\u0013
                \x14 \\u0014    \x15 \\u0015    \x16 \\u0016    \x17 \\u0017
                \x18 \\u0018    \x19 \\u0019    \x1a \\u001a    \x1b \\u001b
                \x1c \\u001c    \x1d \\u001d    \x1e \\u001e    \x1f \\u001f
                \\   \\\\       \"   \\\"
            } $data] \"
        } number {
            # Potential improvement: check for valid numbers.
            append result $data
        } literal {
            # The only valid literals are false, null, and true.
            if {$data ni {false null true}} {
                error "invalid JSON literal \"$data\":\
                        must be false, null, or true"
            }
            append result $data
        }}
    } elseif {[llength $type] >= 2} {
        # Two-element type: first element is array or object, second element is
        # uniform type shared by all array or object values.
        #
        # Longer type: all elements are array or object, except final element
        # may be any supported type.
        if {[llength $type] == 2} {
            set subtype [lindex $type 1]
        } else {
            set subtype [lrange $type 1 end]
        }
        switch [lindex $type 0] {
        array {
            # Recursively encode each array element, using the shared subtype.
            append result \[
            foreach element $data {
                append result $comma [jsonEncode $subtype $element]
                set comma ,
            }
            append result \]
        } object {
            # Recursively encode each object value, using the shared subtype.
            append result \{
            foreach {key value} $data {
                append result $comma [jsonEncode string $key] :\
                        [jsonEncode $subtype $value]
                set comma ,
            }
            append result \}
        }}
    }

    # Confirm the type was recognized.
    if {![info exists result]} {
        error "invalid JSON type \"$type\": must be array, object, string,
                number, literal, or {array|object ?...? subtype} where subtype
                is recursively any valid JSON type"
    }

    # Return the encoded result.
    return $result
}

TODO

  • [jsonDecode]
  • Unit tests