Difference between revisions of "MJSON"
From Scriptwiki
m (Parse string puts a null terminates and reads empty strings correctly now) |
m (→Script) |
||
Line 354: | Line 354: | ||
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 | ||
Line 369: | Line 368: | ||
} | } | ||
} | } | ||
− | |||
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 } | ||
Line 376: | Line 374: | ||
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 | ||
; If we're at the end of a string and not escaped, bail out | ; If we're at the end of a string and not escaped, bail out | ||
Line 389: | Line 386: | ||
; Trim from the start | ; Trim from the start | ||
json.trimstart $1 1 | json.trimstart $1 1 | ||
− | |||
− | |||
; Make sure we haven't consumed all the input | ; Make sure we haven't consumed all the input | ||
if ($bvar($1,0)) { | if ($bvar($1,0)) { | ||
Line 397: | Line 392: | ||
return %bv | return %bv | ||
} | } | ||
− | |||
; $json.parseint(&binvar) - returns an int or error code | ; $json.parseint(&binvar) - returns an int or error code | ||
alias -l json.parseint { | alias -l json.parseint { |
Revision as of 03:31, 23 January 2015
This script is a 100% native way of parsing JSON with an easy API
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 }
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 } } ; 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) :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) :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 } 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- }