Parsing JSON in one go using state machine solution I need to parse a simple JSON string (flat JSON, no hierarchy) for keys and values, and there is a system constraint that I cannot use any built-in JSON library and can only read a string once due to latency requirements. I need to use Python 2.7.x series and cannot use a higher version.

Note: This question is version 3 of my code to solve this problem.

Version 1 - answered by @holroy
Version 2 - answered by @Tersosauros

This is a new version of code based on guidance from @Tersosauros' post of the previous version. Note that I still do not handle some corner cases. I do not want to use two state variables as with @Tersosauros' code, but rewrote using only one state variable. I am not 100% confident if my code is correct logic and your advice is highly appreciated.

READY = 0
COLON = 1
COMMA = 2
STRING = 3
NUMBER = 4

numbers='0123456789.'
NoOps=' {'

def parse(strJSON):
    state = READY
    lastKey=''
    lastValue=''
    temp=''
    result={}
    for c in strJSON:
        if state == READY:
            if c in NoOps:
                continue
            elif c == '"':
                state = STRING
            elif c == ':':
                lastKey=temp
                temp=''
                state = READY
            elif c == ',' or c=='}':
                lastValue=temp
                temp=''
                if lastKey:
                    result[lastKey]=lastValue
                    lastValue=''
                    lastKey=''
                    state=READY
            elif c in numbers:
                state=NUMBER
        elif state == STRING:
            if c == '"':
                state = READY
            else:
                temp += c
        if state == NUMBER:
            if c in NoOps or c==',' or c=='}':
                lastValue=temp
                temp=''
                if lastKey:
                    result[lastKey]=lastValue
                    lastValue=''
                    lastKey=''
                state=READY
            else:
                temp+=c

    if lastKey:
            result[lastKey]=lastValue

    return result

if __name__ == "__main__":
    #JSONString = '{ "id": 1, "name": "A green door", "price": 12.50, "tags": ["home", "green"]}'
    JSONString1 = '{ "id": "1", "name": "A green door", "price": "12.50", "tags": "home green"}'
    JSONString2 = '{ "id": 1, "name": "A green door", "price": 12.50, "tags": "home green"}'
    print parse(JSONString1)
    print parse(JSONString2)
Variable Names

Apart from the odd (non PEP8 compliant) variable casing, you also have a temp variable.  Names like temp are frowned upon as they convey nothing about what that variable represents.

Repeated Tests

You do seem to repeat the tests c==',' or c=='}': a couple of times (lines 26 and 42. I think this could be refactored into using a test string, similar to your NoOps variable.

For example, on line 42, instead of:


        if c in NoOps or c==',' or c=='}':



You could just do

        if c in NoOps or c in CloseOps:


Assuming you had a variable CloseOps defined in the preamble, like this:

 CloseOps = ",}"


The same thing would apply for the repeated use of this test condition on line 26.

Repeated Logic

The following code (lines 45 - 49 inclusive):


        if lastKey:
                result[lastKey]=lastValue
                lastValue=''
                lastKey=''
        state=READY



Appears to be an (almost) verbatim repeat of the logic contained upon lines 29 - 33 (inclusive):


            if lastKey:
                result[lastKey]=lastValue
                lastValue=''
                lastKey=''
                state=READY



Functional Decomposition would dictate that such repeated logic be refactored into it's own function.

Numbers aren't Strings!

There exists a flaw in your code (which was not present in the answer code to your last question), where-by JSON-encoded numbers are turned into strings in your resulting dict.

If you (using your parse function implementation from this question) do:

 &gt;&gt;&gt; type(parse('{"foo":"1"}')['foo'])


This yields:

 &lt;type 'str'&gt;


(Which is fine, since here "1" should be a string.  However:

 &gt;&gt;&gt; type(parse('{"foo":1}')['foo'])


Also yields:

 &lt;type 'str'&gt;


Which indicates an error, as this should be &lt;type 'int'&gt; if the JSON were decoded properly.