partialjson icon indicating copy to clipboard operation
partialjson copied to clipboard

some ideas for improvement

Open tfriedel opened this issue 1 year ago • 7 comments

Hi,

I was dealing with having to parse json returned from ChatGPT and was looking for some kind of best effort json parser, which is how I found this. Good work on doing this! I noticed that the unit tests fail currently for some instances, like parsing "12." to 12. Or the test_incomplete_string fails because no exception is raised. Here I wondered why that would even be the expected behaviour? Shouldn't a string that doesn't end with a " be parsed correctly?

Dealing with incomplete json is only one of the issues, and actually not my main issue. I found that often ChatGPT would include newlines.

For this I found you can use

json_decoder = json.JSONDecoder(strict=False)
json_decoder.decode(s)

This will preserve the newlines, which is not really legal json, but what we want.

There's also a similar project for javascript: https://github.com/beenotung/best-effort-json-parser

And an old python based json parser, that's supposedly also able to parse somewhat illegal json: https://pypi.org/project/demjson/

This one is outdated and doesn't work with newer python version.

There may be some ideas for features or edge cases in those projects to help you improve this library! Would be nice to have a very robust parser for gpt produced json.

tfriedel avatar Dec 06 '23 12:12 tfriedel

Hi Sure! Thank you. If you want to contribute, feel free.

iw4p avatar Dec 06 '23 13:12 iw4p

@tfriedel had the same issues of newlines not being parsed correctly, found this one instead which works for us: https://github.com/promplate/partial-json-parser

korabs-x avatar Apr 04 '24 09:04 korabs-x

@korabs-x Can you give me more details to fix it for the next version?

iw4p avatar Apr 04 '24 09:04 iw4p

Sure! The inconsistency here should make it most clear:

>>> parser.parse('{"x": "1st line\\n2nd line').get('x')
'1st line\\n2nd line'
>>> parser.parse('{"x": "1st line\\n2nd line"').get('x')
'1st line\n2nd line'

In both versions, the \n should not be escaped. Right now it is escaped as long as the string is not finished, and switches to being not escaped once there's the finishing quotes. Hope it helps, thanks for the otherwise great library!

korabs-x avatar Apr 04 '24 09:04 korabs-x

Oh I see! a little bit tricky to handle and fix it, but I'll try to do my best. ty!

iw4p avatar Apr 04 '24 11:04 iw4p

If it helps, this is how I worked around the issues of the LLM output containing real line breaks (using strict=False) and for the partial string case not handling escaped line breaks (by passing it though json.loads).

class LenientJSONParser(JSONParser):
    def parse_string(self, s, e):
        end = s.find('"', 1)
        while end != -1 and s[end - 1] == '\\':  # Handle escaped quotes
            end = s.find('"', end + 1)
        if end == -1:
            # add the missing ending quote, and parse as a json string so that escaping is handled correctly
            return json.loads(s + '"', strict=False), ""
        str_val = s[:end + 1]
        s = s[end + 1:]
        # ignore that actual newline characters might appear in the string using strict=False
        return json.loads(str_val, strict=False), s

pthimon avatar Apr 10 '24 12:04 pthimon

Hi everyone @pthimon @tfriedel @korabs-x Strict mode is added. You can make it false to keep the \n characters.

iw4p avatar Aug 03 '24 18:08 iw4p