Difference between revisions of "MJSON"

From Scriptwiki
Jump to: navigation, search
m
m (line break fix)
 
(11 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
This script is a 100% native way of parsing JSON with an easy API
 
This script is a 100% native way of parsing JSON with an easy API
 +
The script works in 3 phases
 +
1.) Parsing. $json.parse will return the root object, and it can be quite slow. Try to do this as little as possible and re-use the value.
 +
2.) Querying. $json and $mjson can be used to read the data and is generally very fast, but requires a parsed object.
 +
3.) Deleting. json.freeall can be used to delete a json object. Call it on the root object when you are done to clean up.
  
 
==Example==
 
==Example==
Line 47: Line 51:
 
   json.freeall %aEmployees
 
   json.freeall %aEmployees
 
  }
 
  }
 +
 +
$mjson provides a simpler interface for quickly querying data, but it is less powerful than $json. Here is an example of how you can use $mjson to get the name of the first employee
 +
 +
alias json-example-simple {
 +
  bread json-example.txt 0 $file(json-example.txt).size &bvar
 +
 +
  ; Parse top level
 +
  var %aEmployees = $json.parse(&bvar)
 +
 +
  echo -atg The first employee is: $mjson(%aEmployees,0,name)
 +
 +
  json.freeall %aEmployees
 +
}
 +
 +
As you can see, this is a much simpler way to just get information quickly.  However this method does not allow you to get meta-information, such as how many employees there are.
  
 
==Script==
 
==Script==
 
;mJSON by Ben Buzbee (ben@st0rm.net)
 
;
 
;
 
 
  ;$json.parse(&bvar | json string) - Parses a json and returns you a "handle". Use this in the other functions
 
  ;$json.parse(&bvar | json string) - Parses a json and returns you a "handle". Use this in the other functions
 
  ; If the handle is "ERROR N" then N is an error code
 
  ; If the handle is "ERROR N" then N is an error code
;
 
 
  ; $json(handle,N/PROPERTY)  
 
  ; $json(handle,N/PROPERTY)  
  ;  Give a handle (use json.parse first) allows you to index an array get a handle to a property
+
  ;  Given a handle (use json.parse first) allows you to index an array or get a handle to a property by its name
 
  ; .text - Returns a data property or array index as text, e.g. $json($json.parse({"foo":"bar"}),bar).text
 
  ; .text - Returns a data property or array index as text, e.g. $json($json.parse({"foo":"bar"}),bar).text
 
  ; .bvar - Returns a data property or array index as a binvar
 
  ; .bvar - Returns a data property or array index as a binvar
;
 
 
  ; $json.freeall(handle)
 
  ; $json.freeall(handle)
 
  ; Frees memory used to parse the given handle
 
  ; Frees memory used to parse the given handle
  ;
+
  ;$json.parse(&bvar | json string) - Parses a json and returns you a "handle". Use this in the other functions
 
+
; If the handle is "ERROR N" then N is an error code
 
 
 
  alias json.parse {
 
  alias json.parse {
 
   var %bvar = $false
 
   var %bvar = $false
Line 83: Line 95:
 
   elseif ($1 == 2) { return ARRAY }
 
   elseif ($1 == 2) { return ARRAY }
 
   elseif ($1 == 3) { return DATA }
 
   elseif ($1 == 3) { return DATA }
 +
  else return UNKNOWN TYPE $1
 
  }
 
  }
 
  ; $json(handle [,N/PROPERTY])  
 
  ; $json(handle [,N/PROPERTY])  
  ;  Give a handle (use json.parse first) allows you to index an array get a handle to a property
+
  ;  Give a handle (use json.parse first) allows you to
 +
;    Get a property value
 +
;    Index into an array
 +
;    Get the Nth property name of an object
 
  ; .text - Returns a data property or array index as text, e.g. $json($json.parse({"foo":"bar"}),bar).text
 
  ; .text - Returns a data property or array index as text, e.g. $json($json.parse({"foo":"bar"}),bar).text
 
  ; .bvar - Returns a data property or array index as a binvar
 
  ; .bvar - Returns a data property or array index as a binvar
Line 92: Line 108:
 
   var %type = $gettok($1,1,32)
 
   var %type = $gettok($1,1,32)
 
   var %value = $gettok($1,2-,32)
 
   var %value = $gettok($1,2-,32)
   if ($2 isnum && %type != 2) {
+
   if ($2 isnum && %type == 3) {
 
     ; Can't index non array
 
     ; Can't index non array
     echo -atg $json Cannot use indexer on non-array value
+
     echo -atg $json Cannot index data type
 
     return
 
     return
 
   }
 
   }
Line 127: Line 143:
 
   }
 
   }
 
   ; Getting a property of an object
 
   ; Getting a property of an object
   elseif (%type == 1) {
+
   elseif (%type == 1 && $2 !isnum) {
 
     var %proptype = $json.getproperty(%value,$2).type
 
     var %proptype = $json.getproperty(%value,$2).type
 
     var %propvalue = $json.getproperty(%value,$2).value
 
     var %propvalue = $json.getproperty(%value,$2).value
Line 147: Line 163:
 
     else {
 
     else {
 
       return %proptype %propvalue
 
       return %proptype %propvalue
 +
    }
 +
  }
 +
  elseif (%type == 1 && $2 isnum) {
 +
    ; Indexing object by index offset
 +
    var %thisIndex = 0
 +
    var %i = 1
 +
    while ($hget(%value,%i).item) {
 +
      inc %i
 +
      var %item = $ifmatch
 +
      var %propName = $gettok(%item,2-,$asc(.))
 +
      if (type.* iswm %item) {
 +
        inc %thisIndex
 +
      }
 +
      if (%thisIndex == $2) {
 +
        return %propName
 +
      }
 +
      if ($2 == 0) { return %thisIndex }
 +
      return $null
 
     }
 
     }
 
   }
 
   }
Line 152: Line 186:
 
     echo -atg Unknown usage of $!json --> Prop: $prop P1: $1 P2: $2
 
     echo -atg Unknown usage of $!json --> Prop: $prop P1: $1 P2: $2
 
   }
 
   }
 +
}
 +
; mjson is a very thin wrapper to automatically walk down
 +
; the object chain.
 +
alias mjson {
 +
  var %object = $1
 +
  var %propertyIndex = 2
 +
  while (%propertyIndex < $0) {
 +
    var %object = $json(%object,$($ $+ %propertyIndex,2))
 +
    inc %propertyIndex
 +
  }
 +
  ; Return value of last object
 +
  return $json(%object,$($ $+ %propertyIndex,2)).text
 
  }
 
  }
 
  ; Hash table layout:
 
  ; Hash table layout:
Line 161: Line 207:
 
  ; $json.proxyrecurse(idname, param1, param2)
 
  ; $json.proxyrecurse(idname, param1, param2)
 
  alias -l json.proxyrecurse2 {
 
  alias -l json.proxyrecurse2 {
 +
  ; json.debug Recurse on:  return $($+($,$1,$chr(40),$2,$chr(44),$3,$chr(41)),1)
 
   return $($+($,$1,$chr(40),$2,$chr(44),$3,$chr(41)),2)
 
   return $($+($,$1,$chr(40),$2,$chr(44),$3,$chr(41)),2)
 
  }
 
  }
 
  ; /json.freeall <type> <value>
 
  ; /json.freeall <type> <value>
  alias -l json.freeall {
+
  alias json.freeall {
 
   if (!$isid) {
 
   if (!$isid) {
 
     noop $json.proxyrecurse2(json.freeall,$1,$2-)
 
     noop $json.proxyrecurse2(json.freeall,$1,$2-)
Line 178: Line 225:
 
     var %datatable = $gettok($2,1,32)
 
     var %datatable = $gettok($2,1,32)
 
     var %typetable = $gettok($2,2,32)
 
     var %typetable = $gettok($2,2,32)
     json.debug Freeing array, datatable: %datatable - typetable: %typetable
+
     json.debug FreeAll: Freeing array, datatable: %datatable - typetable: %typetable
 
     var %i = 0
 
     var %i = 0
 
     while ($hget(%typetable,%i)) {
 
     while ($hget(%typetable,%i)) {
       noop $json.proxyrecurse2(json.freeall,$ifmatch,$hget(%datatable,%i))
+
      var %elementType = $ifmatch
 +
      if (%elementType == 3) {
 +
        inc %i
 +
        continue
 +
      }
 +
       noop $json.proxyrecurse2(json.freeall,%elementType,$hget(%datatable,%i))
 
       inc %i
 
       inc %i
 
     }
 
     }
Line 308: Line 360:
 
       ; Add array hashtable
 
       ; Add array hashtable
 
       hadd %table value. $+ %strPropName %value
 
       hadd %table value. $+ %strPropName %value
      echo -atg hadd %table value. $+ %strPropName %value
 
      echo -atg $hget(%table,value. $+ %strPropName)
 
 
     }
 
     }
 
     else if (%type == 1) {
 
     else if (%type == 1) {
Line 324: Line 374:
 
   :return
 
   :return
 
   if (%error) {  
 
   if (%error) {  
     json.freeall %table
+
    json.debug ParseObject Exiting with error %error
 +
     json.freeall 1 %table
 
     return %error  
 
     return %error  
 
   }
 
   }
Line 331: Line 382:
 
  ; $json.parsestring(&binvar) - returns string (binvar) or error code
 
  ; $json.parsestring(&binvar) - returns string (binvar) or error code
 
  alias -l json.parsestring {
 
  alias -l json.parsestring {
  var %bv = $json.createbvar
+
  var %bv = $json.createbvar
  if ($bvar($1,1) != $json.chars().STRING_START) { return $json.errors().EXPECTED_START }
+
  if ($bvar($1,1) != $json.chars().STRING_START) { return $json.errors().EXPECTED_START }
  json.trimstart $1 1
+
  json.trimstart $1 1
  var %escape = 0
+
  var %escape = 0
  :loop
+
  :loop
  if ($bvar($1,1) == $json.chars().STRING_ESCAPE) {
+
  if ($bvar($1,1) == $json.chars().STRING_ESCAPE) {
    if (%escape) {
+
    if (%escape) {
      ; If the escape character is escaped, append it
+
      ; If the escape character is escaped, append it
      dec %escape
+
      dec %escape
      goto append
+
      goto append
    }
+
    }
    else {
+
    else {
      inc %escape
+
      inc %escape
      ; Skip appending
+
      ; Skip appending
      goto trim
+
      goto trim
    }
+
    }
  }
+
  }
  if (%escape) {
+
  if (%escape) {
    if ($bvar($1,1) == $asc(r)) { bset $1 1 $cr | dec %escape }
+
    if ($bvar($1,1) == $asc(r)) { bset $1 1 $cr | dec %escape }
    if ($bvar($1,1) == $asc(n)) { bset $1 1 $lf | dec %escape }
+
    if ($bvar($1,1) == $asc(n)) { bset $1 1 $lf | dec %escape }
    if ($bvar($1,1) == $asc(t)) { bset $1 1 9 | dec %escape }
+
    if ($bvar($1,1) == $asc(t)) { bset $1 1 9 | dec %escape }
    if ($bvar($1,1) == $asc(b)) { bset $1 1 8 | dec %escape }
+
    if ($bvar($1,1) == $asc(b)) { bset $1 1 8 | dec %escape }
  }
+
  }
  :append
+
  :append
  ; Add this character to the end
+
  ; If we're at the end of a string and not escaped, bail out
  bset %bv $calc($bvar(%bv,0) +1) $bvar($1,1)
+
  if ($bvar($1,1) == $json.chars().STRING_END && !%escape) {
  :trim
+
    json.trimstart $1 1
  ; Trim from the start
+
    bset %bv $calc($bvar(%bv,0) +1) 0
  json.trimstart $1 1
+
    return %bv
  ; If we're at the end of a string and not escaped, bail out
+
  }   
  if ($bvar($1,1) == $json.chars().STRING_END && !%escape) {
+
  ; Add this character to the end
    json.trimstart $1 1
+
  bset %bv $calc($bvar(%bv,0) +1) $bvar($1,1)
    return %bv
+
  if (%escape) { dec %escape }
  }   
+
  :trim
  ; Make sure we haven't consumed all the input
+
  ; Trim from the start
  if ($bvar($1,0)) {
+
  json.trimstart $1 1
    goto loop
+
  ; Make sure we haven't consumed all the input
  }
+
  if ($bvar($1,0)) {
  return %bv
+
    goto loop
 +
  }
 +
  return %bv
 
  }
 
  }
 
  ; $json.parseint(&binvar) - returns an int or error code
 
  ; $json.parseint(&binvar) - returns an int or error code
Line 381: Line 434:
 
   return %bv
 
   return %bv
 
  }
 
  }
  $json.parsearray - returns array tables (data, types) or error code
+
  ; $json.parsearray - returns array tables (data, types) or error code
 
  alias -l json.parsearray {
 
  alias -l json.parsearray {
  if ($bvar($1,1) != $json.chars().ARRAY_START) {
+
  if ($bvar($1,1) != $json.chars().ARRAY_START) {
    return $json.errors().EXPECTED_START
+
    return $json.errors().EXPECTED_START
  }
+
  }
  json.trimstart $1 1
+
  json.trimstart $1 1
  ; Create a linked list for the data
+
  ; Create a linked list for the data
  var %datatable = $json.createtable(1)
+
  var %datatable = $json.createtable(1)
  ; Create a linked list for the types (associative with respect to the data list)
+
  ; Create a linked list for the types (associative with respect to the data list)
  var %typetable = $json.createtable(1)
+
  var %typetable = $json.createtable(1)
  :member
+
  noop $json.consumewhitespace($1)
  noop $json.consumewhitespace($1)
+
  if ($bvar($1,1) == $json.chars().ARRAY_END) {
  ; Parse members
+
    json.debug Parsed empty array successfully
  var %any = $json.parseany($1)
+
    json.trimstart $1 1
  var %type = $gettok(%any,1,32)
+
    return %datatable %typetable
  var %value = $gettok(%any,2-,32)
+
  }
  if (%type == ERROR) {
+
  :member
    json.debug Error parsing array member: %value
+
  noop $json.consumewhitespace($1)
    json.freeall 2 %datatable %typetable
+
  ; Parse members
    return %value
+
  var %any = $json.parseany($1)
  }
+
  var %type = $gettok(%any,1,32)
  else {
+
  var %value = $gettok(%any,2-,32)
    ; 0 indexed
+
  if (%type == ERROR) {
    var %newindex = $calc($hget(%datatable,0).item)
+
    json.debug Error parsing array member: %value
    ; Binary data
+
    json.freeall 2 %datatable %typetable
    json.debug Add type %type array index %newindex as %value
+
    return %value
    if (%type == 3) {
+
  }
      hadd -b %datatable %newindex %value
+
  else {
    }
+
    ; 0 indexed
    else {
+
    var %newindex = $calc($hget(%datatable,0).item)
      hadd %datatable %newindex %value
+
    ; Binary data
    }
+
    json.debug ParseArray: Add type %type array index %newindex as %value
    hadd %typetable %newindex %type
+
    if (%type == 3) {
  }
+
      hadd -b %datatable %newindex %value
  noop $json.consumewhitespace($1)
+
    }
  if ($bvar($1,1) == $json.chars().DELIMITER) {
+
    else {
    json.trimstart $1 1
+
      hadd %datatable %newindex %value
    goto member
+
    }
  }
+
    hadd %typetable %newindex %type
  elseif ($bvar($1,1) == $json.chars().ARRAY_END) {
+
  }
    json.debug Parsed array successfully
+
  noop $json.consumewhitespace($1)
    json.trimstart $1 1
+
  if ($bvar($1,1) == $json.chars().DELIMITER) {
    return %datatable %typetable
+
    json.trimstart $1 1
  }
+
    goto member
  json.freeall 2 %datatable %typetable
+
  }
  return $json.errors().UNKNOWN
+
  elseif ($bvar($1,1) == $json.chars().ARRAY_END) {
 +
    json.debug Parsed array successfully
 +
    json.trimstart $1 1
 +
    return %datatable %typetable
 +
  }
 +
  :exit
 +
  json.debug ParseArray failed with an unknown error. Read characters: $bvar($1,1)
 +
  json.freeall 2 %datatable %typetable
 +
  return $json.errors().UNEXPECTED_CHAR
 
  }
 
  }
 
  ; $json.consumewhitespace(&binvar) - returns number consumed
 
  ; $json.consumewhitespace(&binvar) - returns number consumed
Line 495: Line 556:
 
   ; echo -atg Debug: $1-
 
   ; echo -atg Debug: $1-
 
  }
 
  }
 
{{Author|aca20031}}
 
 
[[Category:Script Archive]]
 

Latest revision as of 20:31, 22 May 2015

This script is a 100% native way of parsing JSON with an easy API The script works in 3 phases 1.) Parsing. $json.parse will return the root object, and it can be quite slow. Try to do this as little as possible and re-use the value. 2.) Querying. $json and $mjson can be used to read the data and is generally very fast, but requires a parsed object. 3.) Deleting. json.freeall can be used to delete a json object. Call it on the root object when you are done to clean up.

Example

To use the below example, Save the following JSON in "json-example.txt" in $mircdir

[
   {
       "name":"Todd",
       "IsManager":false,
       "Spouse":{"Name":"Marie"},
       "Sallary":100000
   },
   {
       "name":"Gena",
       "IsManager":true,
       "Spouse":null,
       "Sallary":157103e-3
   }
]


And then run /json-example with the script loaded.

alias json-example {
 bread json-example.txt 0 $file(json-example.txt).size &bvar

 ; Parse top level
 var %aEmployees = $json.parse(&bvar)
 echo -atg The top level is a(n) $json.type(%aEmployees)

 var %cEmployees = $json(%aEmployees).length
 echo -atg Number of employees: %cEmployees

 var %i = 0
 while (%i < %cEmployees) {
   var %oEmployee = $json(%aEmployees,%i)
   inc %i

   echo -atg Name: $json(%oEmployee,name).text
   echo -atg IsManager: $json(%oEmployee,ismanager).text
   var %spouse = $json(%oEmployee,spouse)
   if (%spouse) { echo -atg Spouse: $json(%spouse,name).text  }
   echo -atg Sallary: $json(%oEmployee,Sallary).text
 }

 ; Free memory
 json.freeall %aEmployees
}

$mjson provides a simpler interface for quickly querying data, but it is less powerful than $json. Here is an example of how you can use $mjson to get the name of the first employee

alias json-example-simple {
 bread json-example.txt 0 $file(json-example.txt).size &bvar

 ; Parse top level
 var %aEmployees = $json.parse(&bvar)

 echo -atg The first employee is: $mjson(%aEmployees,0,name)

 json.freeall %aEmployees
}

As you can see, this is a much simpler way to just get information quickly. However this method does not allow you to get meta-information, such as how many employees there are.

Script

;$json.parse(&bvar | json string) - Parses a json and returns you a "handle". Use this in the other functions
; If the handle is "ERROR N" then N is an error code
; $json(handle,N/PROPERTY) 
;  Given a handle (use json.parse first) allows you to index an array or get a handle to a property by its name
; .text - Returns a data property or array index as text, e.g. $json($json.parse({"foo":"bar"}),bar).text
; .bvar - Returns a data property or array index as a binvar
; $json.freeall(handle)
; Frees memory used to parse the given handle
;$json.parse(&bvar | json string) - Parses a json and returns you a "handle". Use this in the other functions
; If the handle is "ERROR N" then N is an error code
alias json.parse {
  var %bvar = $false
  if (&* iswm $1) {
    %bvar = $1
  }
  else {
    %bvar = $json.createbvar
    bset -t %bvar 1 $1-
  }
  return $json.parseany(%bvar)
}
; $json.type(handle) - Returns ARRAY, OBJECT, or Data
alias json.type {
  tokenize 32 $1-
  if ($1 == 1) { return OBJECT }
  elseif ($1 == 2) { return ARRAY }
  elseif ($1 == 3) { return DATA }
  else return UNKNOWN TYPE $1
}
; $json(handle [,N/PROPERTY]) 
;  Give a handle (use json.parse first) allows you to
;    Get a property value
;    Index into an array
;    Get the Nth property name of an object
; .text - Returns a data property or array index as text, e.g. $json($json.parse({"foo":"bar"}),bar).text
; .bvar - Returns a data property or array index as a binvar
; .length - If the property is an array, or the handle is an array that isn't indexed, returns its length
alias json {
  var %type = $gettok($1,1,32)
  var %value = $gettok($1,2-,32)
  if ($2 isnum && %type == 3) {
    ; Can't index non array
    echo -atg $json Cannot index data type
    return
  }
  ; Indexing into an array
  if ($2 isnum && %type == 2) {
    var %datatable = $gettok(%value,1,32)
    var %typetable = $gettok(%value,2,32)
    var %thistype = $hget(%typetable,$2)
    var %thisdata = $hget(%datatable,$2)
    if (!%thistype) { return $null }
    if (%thistype == 3 && $prop == bvar) {
      var %bv = $json.createbvar
      noop $hget(%datatable,$2,%bv) 
      return %bv
    }
    elseif (%thistype == 3 && $prop == text) {
      return %thisdata
    }
    elseif (%thistype == 3) {
      var %bv = $json.createbvar
      noop $hget(%datatable,$2,%bv) 
      return %thistype %bv
    }
    elseif (%thistype != 3) {
      return %thistype %thisdata
    }
  }
  elseif (%type == 2 && $prop == length) {
    ; If we're not indexing, but we have an array with the .length property, return its length
    ; Return length of the data table for the array
    return $hget($gettok(%value,1,32),0).item
  }
  ; Getting a property of an object
  elseif (%type == 1 && $2 !isnum) {
    var %proptype = $json.getproperty(%value,$2).type
    var %propvalue = $json.getproperty(%value,$2).value
    var %propbv = $json.getproperty(%value,$2).bvar
    if (!%proptype) { return $null }
    if (%proptype == 3 && $prop == text) {
      return %propvalue
    } 
    elseif (%proptype == 3 && $prop == bvar) {
      return %propbv
    }
    elseif (%proptype == 3) {
      return %proptype %propbv
    }
    elseif (%proptype == 2 && $prop == length) {
      ; Return length of the data table for the array
      return $hget($gettok(%propvalue,1,32),0).item
    }
    else {
      return %proptype %propvalue
    }
  }
  elseif (%type == 1 && $2 isnum) {
    ; Indexing object by index offset
    var %thisIndex = 0
    var %i = 1
    while ($hget(%value,%i).item) {
      inc %i
      var %item = $ifmatch
      var %propName = $gettok(%item,2-,$asc(.))
      if (type.* iswm %item) {
        inc %thisIndex
      }
      if (%thisIndex == $2) {
        return %propName
      }
      if ($2 == 0) { return %thisIndex }
      return $null
    }
  }
  else {
    echo -atg Unknown usage of $!json --> Prop: $prop P1: $1 P2: $2
  }
}
; mjson is a very thin wrapper to automatically walk down
; the object chain. 
alias mjson {
 var %object = $1
 var %propertyIndex = 2
 while (%propertyIndex < $0) {
   var %object = $json(%object,$($ $+ %propertyIndex,2))
   inc %propertyIndex
 }
 ; Return value of last object
 return $json(%object,$($ $+ %propertyIndex,2)).text
}
; Hash table layout:
;   Name: json.<ticks><rand1,4294967295>
;   Key: Name of json property
;   Value <N> <value>
;       N: 1 = pointer to another object, 3 = raw value
;          2 = <pointer to data array> <pointer to assoc array indicating types of data array>
; $json.proxyrecurse(idname, param1, param2)
alias -l json.proxyrecurse2 {
  ; json.debug Recurse on:   return $($+($,$1,$chr(40),$2,$chr(44),$3,$chr(41)),1)
  return $($+($,$1,$chr(40),$2,$chr(44),$3,$chr(41)),2)
}
; /json.freeall <type> <value>
alias json.freeall {
  if (!$isid) {
    noop $json.proxyrecurse2(json.freeall,$1,$2-)
    return
  }
  json.debug ?4freeall $1-
  ; For binary data, just return
  if ($1 == 3) {
    return
  }
  ; For arrays, call on each element then hfree tables
  elseif ($1 == 2) {
    var %datatable = $gettok($2,1,32)
    var %typetable = $gettok($2,2,32)
    json.debug FreeAll: Freeing array, datatable: %datatable - typetable: %typetable
    var %i = 0
    while ($hget(%typetable,%i)) {
      var %elementType = $ifmatch
      if (%elementType == 3) {
        inc %i
        continue
      }
      noop $json.proxyrecurse2(json.freeall,%elementType,$hget(%datatable,%i))
      inc %i
    }
    .hfree %datatable
    .hfree %typetable
  }
  ; For objects, call on every property then hfree table
  elseif ($1 == 1) {
    var %i = 1
    while ($hget($2,%i).item) {
      inc %i
      var %item = $ifmatch
      if (type.* iswm %item) {
        var %membername = $gettok(%item,2-,$asc(.))
        noop $json.proxyrecurse2(json.freeall,$hget($2,%item), $hget($2,value. $+ %membername))
      }
    }
    .hfree $2
  }
}
; Parses an array, object, string or int, depending on the next character, and returns <type> <value>
; or returns "ERROR <error>
alias -l json.parseany {
  ; Now, read the value of the property
  if ($bvar($1,1) == $json.chars().STRING_START) { 
    json.debug Parseany detected a string
    var %strVal = $json.parsestring($1)
    if (%strVal isnum) {
      json.debug Error parsing string: %error
      return ERROR %strVal
    }
    json.debug ParseAny: Read string value: $bvar(%strval,1-).text
    return 3 %strval
  }
  elseif ($bvar($1,1) == $json.chars().OBJECT_START) {
    json.debug ParseAny: detected an object
    var %obj = $json.parseobject($1)
    if (%obj isnum) { json.debug ParseAny: Error reading object subvalue ( $+ %obj $+ ) | return ERROR %obj } 
    json.debug ParseAny: Read object subvalue as %obj
    return 1 %obj
  }
  elseif ($bvar($1,1) == $json.chars().ARRAY_START) {
    json.debug Parseany detected an array
    var %array = $json.parsearray($1)
    if (%array isnum) {
      json.debug ParseAny: error reading array subvalue: %array
      return ERROR %array
    }
    json.debug ParseAny: Read array subvalue as %array
    return 2 %array
  }
  elseif ($bvar($1,1,4).text == null) {
    json.debug ParseAny found NULL value
    json.trimstart $1 4
    return 3 $null
  }
  elseif ($bvar($1,1,5).text == false || $bvar($1,1,4).text == true) {
    var %value = $iif($bvar($1,1,5).text == false,$false,$true)
    ; if true, trim 'true' off the top, otherwise trim 'false'
    if (%value) { json.trimstart $1 4 }
    else { json.trimstart $1 5 }
    json.debug ParseAny found bool value: %value
    return 3 %value
  }
  ; Must be an int
  else {
    var %int = $json.parseint($1) 
    if (%int isnum) { 
      json.debug Error reading int subvalue: %int
      return ERROR %int
    }
    if ($bvar(%int,0) == 0) {
      return ERROR $json.errors().UNEXPECTED_CHAR
    }
    json.debug Read int subvalue into var %int as $bvar(%int,1-).text
    return 3 %int
  }
}
; $json.parseobject(&binvar) - returns object name or error code
alias -l json.parseobject {
  if ($bvar($1,1) == $json.chars().OBJECT_START) {
    ; Trim off {
    json.trimstart $1 1
  }
  var %table = $json.createtable
  var %error = 0
  :property
  noop $json.consumewhitespace($1)
  if ($bvar($1,1) != $json.chars().STRING_START) { %error = $json.errors().EXPECTED_START | goto return }
  ; Property name
  var %strPropName = $json.parsestring($1)
  if (%strPropName isnum) { json.debug Error reading property name for object $1 }
  else {
    %strPropName = $bvar(%strPropName,1-).text
    json.debug ParseObject: Read property %strPropName
    noop $json.consumewhitespace($1)
    if ($bvar($1,1) != $json.chars().MAPPING) { %error = $json.errors().EXPECTED_MAPPING | goto return }
    ; trim the mapping character
    json.trimstart $1 1
    noop $json.consumewhitespace($1)
    var %any = $json.parseany($1)
    var %type = $gettok(%any,1,32)
    var %value = $gettok(%any,2-,32)
    if (%type == ERROR) {
      %error = %value
      goto return
    }
    if (%type == 3) {
      ; rvalue
      hadd %table type. $+ %strPropName 3
      if (%value == $true || %value == $false) {
        json.debug PasrseObject; Found boolean value for %strPropName : %value
        hadd %table value. $+ %strPropName %value
      }
      elseif (!$bvar(%value,0)) {
        hadd %table value. $+ %strPropName $null
      }
      else {
        hadd -b %table value. $+ %strPropName %value
      }
    }
    else if (%type == 2) {
      ; Arrays
      json.debug Read array %strPropName as %value for object %table
      hadd %table type. $+ %strPropName 2
      ; Add array hashtable
      hadd %table value. $+ %strPropName %value
    }
    else if (%type == 1) {
      hadd %table type. $+ %strPropName 1
      ; Add object naem to table
      hadd %table value. $+ %strPropName %value
    }
  }
  ; Check to see if there are more properties
  noop $json.consumewhitespace($1)
  if ($bvar($1,1) == $json.chars().OBJECT_END) { json.debug Successful object parsing. | json.trimstart $1 1 | goto return }
  elseif ($bvar($1,1) == $json.chars().DELIMITER) { json.trimstart $1 1 | goto property }
  else { return $json.errors().UNEXPECTED_CHAR }
  :return
  if (%error) { 
    json.debug ParseObject Exiting with error %error
    json.freeall 1 %table
    return %error 
  }
  return %table
}
; $json.parsestring(&binvar) - returns string (binvar) or error code
alias -l json.parsestring {
 var %bv = $json.createbvar
 if ($bvar($1,1) != $json.chars().STRING_START) { return $json.errors().EXPECTED_START }
 json.trimstart $1 1
 var %escape = 0
 :loop
 if ($bvar($1,1) == $json.chars().STRING_ESCAPE) {
   if (%escape) {
     ; If the escape character is escaped, append it
     dec %escape
     goto append
   }
   else {
     inc %escape
     ; Skip appending
     goto trim
   }
 }
 if (%escape) {
   if ($bvar($1,1) == $asc(r)) { bset $1 1 $cr | dec %escape }
   if ($bvar($1,1) == $asc(n)) { bset $1 1 $lf | dec %escape }
   if ($bvar($1,1) == $asc(t)) { bset $1 1 9 | dec %escape }
   if ($bvar($1,1) == $asc(b)) { bset $1 1 8 | dec %escape }
 }
 :append
 ; If we're at the end of a string and not escaped, bail out
 if ($bvar($1,1) == $json.chars().STRING_END && !%escape) {
   json.trimstart $1 1
   bset %bv $calc($bvar(%bv,0) +1) 0
   return %bv
 }  
 ; Add this character to the end
 bset %bv $calc($bvar(%bv,0) +1) $bvar($1,1)
 if (%escape) { dec %escape }
 :trim
 ; Trim from the start
 json.trimstart $1 1
 ; Make sure we haven't consumed all the input
 if ($bvar($1,0)) {
   goto loop
 }
 return %bv
}
; $json.parseint(&binvar) - returns an int or error code
alias -l json.parseint {
  var %bv = &json.createbvar
  ; Lax on the number format since its a string for us anyway
  while ($bvar($1,1).text isnum || $bvar($1,1) == $asc(.) || $bvar($1,1) == $asc(-) || $bvar($1,1) == $asc(+) || $bvar($1,1) == $asc(e)) {
    bset %bv $calc($bvar(%bv,0) +1) $bvar($1,1)
    json.trimstart $1 1
  }
  return %bv
}
; $json.parsearray - returns array tables (data, types) or error code
alias -l json.parsearray {
 if ($bvar($1,1) != $json.chars().ARRAY_START) {
   return $json.errors().EXPECTED_START
 }
 json.trimstart $1 1
 ; Create a linked list for the data
 var %datatable = $json.createtable(1)
 ; Create a linked list for the types (associative with respect to the data list)
 var %typetable = $json.createtable(1)
 noop $json.consumewhitespace($1)
 if ($bvar($1,1) == $json.chars().ARRAY_END) {
   json.debug Parsed empty array successfully
   json.trimstart $1 1
   return %datatable %typetable
 }
 :member
 noop $json.consumewhitespace($1)
 ; Parse members
 var %any = $json.parseany($1)
 var %type = $gettok(%any,1,32)
 var %value = $gettok(%any,2-,32)
 if (%type == ERROR) {
   json.debug Error parsing array member: %value
   json.freeall 2 %datatable %typetable
   return %value
 }
 else {
   ; 0 indexed
   var %newindex = $calc($hget(%datatable,0).item)
   ; Binary data
   json.debug ParseArray: Add type %type array index %newindex as %value
   if (%type == 3) {
     hadd -b %datatable %newindex %value
   }
   else {
     hadd %datatable %newindex %value
   }
   hadd %typetable %newindex %type
 }
 noop $json.consumewhitespace($1)
 if ($bvar($1,1) == $json.chars().DELIMITER) {
   json.trimstart $1 1
   goto member
 }
 elseif ($bvar($1,1) == $json.chars().ARRAY_END) {
   json.debug Parsed array successfully
   json.trimstart $1 1
   return %datatable %typetable
 }
 :exit
 json.debug ParseArray failed with an unknown error. Read characters: $bvar($1,1)
 json.freeall 2 %datatable %typetable
 return $json.errors().UNEXPECTED_CHAR
}
; $json.consumewhitespace(&binvar) - returns number consumed
alias -l json.consumewhitespace {
  while ($bvar($1,0) && ($bvar($1,1) == 32 || $bvar($1,1) == 9 || $bvar($1,1) == 160 || $bvar($1,1) == 10 || $bvar($1,1) == 13)) {
    json.trimstart $1 1
  }
}
; /json.trimstart &binvar length - trims length characters from the start of $binvar
alias -l json.trimstart {
  ; json.debug Trimstart: $1 $2 -- Size of bvar: $bvar($1,0)
  if ($bvar($1,0) == 1) { bunset $1 }
  else { bcopy -c $1 1 $1 $calc($2 +1) -1 }
}
; $json.getpropery(table, property).type - Returns number representing type of property
; $json.getproperty(table, property).value - Returns value of property
; $json.getproperty(table, property).bvar - Returns value of property as bvar
; $json.getproperty(table, property).arraytypetable - If property is array, returns its type table
; $json.getproperty(table, property).arraydatatable - If property is table,r eturns its data table
alias -l json.getproperty {
  var %type = $hget($1,type. $+ $2)
  var %value = $hget($1,value. $+ $2)
  if ($prop == type) { return %type }
  if ($prop == bvar) { noop $hget($1, value. $+ $2, &bvReturn) | return &bvReturn }
  if ($prop == arraytypetable) { return $gettok(%value,2,32) }
  if ($prop == arraydatatable) { return $gettok(%value,1,32) }
  return %value
}
; $json.createtable - Creates a hashtable and returns its name
; $json.createtable(N) - gives it size N
alias -l json.createtable {
  var %ticks = $ticks
  var %rand = $rand(1,4294967295)
  var %table = $+(json.,%ticks,.,%rand)
  hmake %table $iif($1,$1,10)
  return %table
}
; Creates a random binvar name to avoid collisions
alias -l json.createbvar {
  var %ticks = $ticks
  var %rand = $rand(1,4294967295)
  var %bv = $+(&jsonbv.,%ticks,.,%rand)
  return %bv
}
alias -l json.chars {
  ; {
  if ($prop == OBJECT_START) { return 123 } 
  ; }
  if ($prop == OBJECT_END) { return 125 } 
  ; "
  if ($prop == STRING_START || $prop == STRING_END) { return 34 }
  if ($prop == STRING_ESCAPE) { return $asc(\) }
  if ($prop == ARRAY_START) { return $asc([) }
  if ($prop == ARRAY_END) { return $asc(]) }
  ; ,
  if ($prop == DELIMITER) { return 44 }
  if ($prop == MAPPING) { return $asc(:) }
}
alias -l json.errors {
  if ($prop == EXPECTED_START) { return 1 }
  if ($prop == EXPECTED_END) { return 2 }
  if ($prop == EXPECTED_DELIMITER) { return 3 }
  if ($prop == EXPECTED_MAPPING) { return 4 }
  if ($prop == UNEXPECTED_CHAR) { return 5 }
  return 255
}
alias -l json.debug {
  ; echo -atg Debug: $1-
}