For my competition entry, I needed to parse some JSON data. It turns out that the data I was getting wasn't really suitable for what I wanted it to do, so I'm sharing this piece early.
It will parse JSON data like a SAX parser will parse XML. It makes callbacks to a listener on events, rather than giving you a traversable object model. I've not tried this with a wide range of data, but I'll help out if you find some data it isn't working well with.
There are six events it raises:
- objectStart(); //Called when the parser finds a curly open brace {[/*:m]
- objectEnd(); //Called when the parser finds a curly close brace }[/*:m]
- arrayStart(); //Called when the parser finds a square open brace [[/*:m]
- arrayEnd(); //Called when the parser finds a square close brace ][/*:m]
- header(String& name); //Called when the parser finds the Name part of a name/value pair[/*:m]
- data(String& value); //Called when the parser finds the Value part of the name/value pair[/*:m]
Unlike the MTXml objects, this only really expects a complete JSON string. It doesn't store left over data for you to append to later, as it seemed to be much harder to tell when you've finished with JSON data than with XML, and JSON data also generally seems to be very small. Pass a complete JSON string to the parser.
JSONParser.h
/*
* JSONParser.h
*
* Created on: 21 Feb 2010
* Author: sjp
*/
#ifndef JSONCONNECTION_H_
#define JSONCONNECTION_H_
#include <MAUtil/String.h>
using namespace MAUtil;
class JSONListener
{
public:
virtual void header(String& header) = 0;
virtual void data(String& value) = 0;
virtual void arrayStart() = 0;
virtual void arrayEnd() = 0;
virtual void objectStart() = 0;
virtual void objectEnd() = 0;
};
class JSONParser
{
public:
JSONParser();
~JSONParser();
void parseString(String& jsondata);
void addListener(JSONListener* listener);
void reset();
private:
String data;
String temp;
int _start;
int _end;
Vector<JSONListener*> _listeners;
void parse();
int findNextQuote(int startPos);
int nextDelimiter(int startPos);
int findPosition(char c, int startPos);
bool gotHeader;
};
#endif /* JSONCONNECTION_H_ */
/*
* JSONParser.cpp
*
* Created on: 21 Feb 2010
* Author: sjp
*/
#include "JSONParser.h"
#define LOG lprintfln
JSONParser::JSONParser()
{
}
JSONParser::~JSONParser()
{
}
void JSONParser::reset()
{
_start = 0;
data.clear();
temp.clear();
gotHeader = false;
}
void JSONParser::parseString(String& jsondata)
{
LOG("Starting JSON parse");
data = jsondata;
_start = 0;
parse();
}
void JSONParser::parse()
{
while(_start < data.length())
{
//Move through white space
char firstChar = data.c_str()[_start];
while(firstChar < 33)
firstChar = data.c_str()[++_start];
//Check the first character, if it is an [,],{ or } raise the approriate event
LOG("First char:%c", firstChar);
if(firstChar == '[')
{
Vector_each(JSONListener*, itr, _listeners)
(*itr)->arrayStart();
_start++;
gotHeader = false;
}
else if(firstChar == ']')
{
Vector_each(JSONListener*, itr, _listeners)
(*itr)->arrayEnd();
_start++;
gotHeader = false;
}
else if(firstChar == '{')
{
Vector_each(JSONListener*, itr, _listeners)
(*itr)->objectStart();
_start++;
gotHeader = false;
}
else if(firstChar == '}')
{
Vector_each(JSONListener*, itr, _listeners)
(*itr)->objectEnd();
_start++;
gotHeader = false;
}
else if(firstChar == ',')
{
_start++;
gotHeader = false;
}
else if(firstChar == ':')
{
_start++;
gotHeader = true;
}
else if(!gotHeader)
{
//read the header
//LOG("Reading header");
temp.clear();
_end = data.findFirstOf(':', _start);
lprintfln("start %d end %d len %d", _start, _end, _end - _start);
_end -= _start; //get the length
if(_end >
{
temp = data.substr(_start, _end);
//LOG("Header: '%s'", temp.c_str());
_start += _end;
Vector_each(JSONListener*, itr, _listeners)
(*itr)->header(temp);
}
}
else
{
//Processing data
if(firstChar == '\"')
{
//LOG("Reading string");
//Processing a string
_end = findNextQuote(_start + 1) + 1;
//LOG("Next quote at %d", _end);
_end -= _start;
//LOG("Giving a length of %d", _end);
if(_end > 150)
temp = data.substr(_start + 1, 150); //don't take the leading quote, it won't have a pair
else
{
//LOG("Taking substring %d to %d", _start, _end);
temp = data.substr(_start, _end);
}
//LOG("Got string");
_start += _end;
Vector_each(JSONListener*, itr, _listeners)
(*itr)->data(temp);
}
else
{
//LOG("Reading value");
_end = nextDelimiter(_start);
_end -= _start;
temp = data.substr(_start, _end);
//LOG("Value '%s'", temp.c_str());
_start += _end;
Vector_each(JSONListener*, itr, _listeners)
(*itr)->data(temp);
}
}
}
}
int JSONParser::findNextQuote(int startPos)
{
LOG("Looking for quote from %d", startPos);
startPos = data.findFirstOf('\"', startPos);
LOG("Checking previous char to %d '%c'", startPos, data.c_str()[startPos-1]);
if(data.c_str()[startPos-1] == '\\')
startPos = findNextQuote(startPos + 1); //Ignore any \" pairs in the string data
LOG("Returning quote position at %d", startPos);
return startPos;
}
void JSONParser::addListener(JSONListener* listener)
{
_listeners.add(listener);
}
int JSONParser::nextDelimiter(int startPos)
{
int nextComma = findPosition(',', startPos);
int nextSquareBrace = findPosition(']', startPos);
int nextCurlyBrace = findPosition('}', startPos);
int lowest = nextComma < nextSquareBrace ? nextComma : nextSquareBrace;
if(nextCurlyBrace < lowest)
lowest = nextCurlyBrace;
return lowest;
}
int JSONParser::findPosition(char c, int startPos)
{
int cpos = data.findFirstOf(c, startPos);
if(cpos == -1)
cpos = data.length();
return cpos;
}
[/]
