Parsing JSON

4 posts / 0 new
Last post
Sam Pickard
rival's picture
Offline
Mobile Archmage
Joined: 19 Mar 2009
Posts:
Parsing JSON

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 > Innocent
      {
        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;
}

[/]

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
fatalerror
fatalerror's picture
Offline
Mobile Conjurer
Joined: 2 Oct 2009
Posts:

I like it Wink
Good work!

Sam Pickard
rival's picture
Offline
Mobile Archmage
Joined: 19 Mar 2009
Posts:

I've updated the code above with a new version which doesn't remove characters from the string. I've also found that I need to transcode a lot of JSON data before parsing as it is in UTF8 and it can't seem to count characters properly - but looking at the new version of String, then I think this issue is being sorted.

Sam Pickard
rival's picture
Offline
Mobile Archmage
Joined: 19 Mar 2009
Posts:

Another update. The parsing code is now iterative rather than recursive. Should save quite a few cycles each time its run