Skip to content

Instantly share code, notes, and snippets.

@jdp
Created January 3, 2009 03:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jdp/42776 to your computer and use it in GitHub Desktop.
Save jdp/42776 to your computer and use it in GitHub Desktop.
Simple JSON parser for Io
/*
* JsonParser.io
* A simple JSON parser for Io
* Copyright (c) 2008 Justin Poliey <jdp34@njit.edu>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Usage:
*
* obj := JsonParser clone setJson("json_data") parse
* obj := JsonParser clone with("json_data") parse
* obj := "json_data" parseJson
*
*/
/* Reference Regex addon */
Regex
/* The JSON parser object */
JsonParser := Object clone do(
/* The raw JSON data */
json ::= "{}"
/* Position of the character scanner */
scan := 0
/* Return n characters at the current position */
peekToken := method(n,
n = n ifNilEval(1)
return if(scan + n < json size, json exSlice(scan, scan + n), nil)
)
/* Moves the scanner forward n characters */
nextToken := method(n,
scan = scan + n ifNilEval(1)
return(peekToken)
)
/* Raises an error when expecting a character */
expectToken := method(ch,
if(ch != peekToken, Exception raise("'#{peekToken}' at #{scan} found, but expected '#{ch}'" interpolate))
)
/* Skip whitespace */
eat := method(
whitespace := list("\n", "\r", "\t", " ")
while(whitespace contains(peekToken), nextToken)
)
/* Convenience method */
with := method(rawjson,
setJson(rawjson)
)
/* Parses JSON and returns an Io representation */
parse := method(
eat
if(peekToken == "{", return readObject)
if(peekToken == "[", return readArray)
if(peekToken == "\"", return readString)
if(peekToken matchesRegex("[0-9\\+\\-]"), return readNumber)
if(peekToken == "t", return readTrue)
if(peekToken == "f", return readFalse)
if(peekToken == "n", return readNull)
if(peekToken == "/", readComment; return parse)
Exception raise("Malformed JSON")
)
/* Skip over a comment */
readComment := method(
nextToken
if(peekToken == "*",
nextToken
while(peekToken(2) != "*/",
nextToken
)
nextToken(2)
return
)
if(peekToken == "/",
while(list("\n", "\r") contains(peekToken) not,
nextToken
)
return
)
)
/* Look for a specific literal value */
readLiteral := method(literal,
if(peekToken(literal size) == literal,
nextToken(literal size)
return true
,
return false
)
)
/* Returns a boolean true from JSON */
readTrue := method(
if(readLiteral("true"), return true, Exception raise("Invalid literal"))
)
/* Returns a boolean false from JSON */
readFalse := method(
if(readLiteral("false"), return false, Exception raise("Invalid literal"))
)
/* Returns a null value from JSON */
readNull := method(
if(readLiteral("null"), return nil, Exception raise("Invalid literal"))
)
/* Reads a string in JSON format */
readString := method(
result := Sequence clone asMutable
nextToken /* skip initial quote */
while(peekToken != "\"",
if(peekToken == "\\",
nextToken
if(peekToken == "u",
nextToken
result append(peekToken(4) fromBase(16))
nextToken(4)
,
result appendSeq("\\") appendSeq(nextToken)
)
,
result appendSeq(peekToken)
nextToken
)
)
nextToken
return(result unescape)
)
/* Reads a number in JSON format */
readNumber := method(
result := Sequence clone asMutable
while(peekToken matchesRegex("[0-9\\+\\-e]"),
result appendSeq(peekToken)
nextToken
)
return result asNumber
)
/* Reads an object in JSON format */
readObject := method(
result := Object clone
loop(
/* Read a key, enforce it being a string */
nextToken
eat
/* Allow empty objects */
if(peekToken == "}", nextToken; break)
key := parse
if(key type != "Sequence", Exception raise("JSON object keys must be strings"))
/* Key/value pairs are colon separated */
eat
expectToken(":")
nextToken
eat
value := parse
result setSlot(key, value)
/* Either end object or get next pair */
eat
if(peekToken == "}", break)
expectToken(",")
)
nextToken
return result
)
/* Reads an array in JSON format */
readArray := method(
result := List clone
loop(
/* Read a value */
nextToken
eat
value := parse
result append(value)
/* Add another value or end array */
eat
if(peekToken == "]", break)
expectToken(",")
)
nextToken
return result
)
)
/* A useful method to make an object from a sequence */
Sequence parseJson := method(
json := JsonParser clone setJson(self) parse
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment