Search code examples
pythonjsonmongodbbson

Is there a python decoder that produces VALID json and handles NaNs


I want to serialize mongoDb's output to actual VALID json in python. I've found several libraries, but none of them seems to both produce valid json and handle incompatible values with null (undefined would be fine also).

Example input:

{
    "_id" : ObjectId("57f3e32c71285f85b46d6fb5"),
    "rate" : Infinity
    "value" : NaN
}

Expected output:

(tried json.dumps and its allow_nan option, but it throws errors and it doesn't help at all)

{
    "_id" : { $oid: "57f3e32c71285f85b46d6fb5" },
    "rate" : null,
    "value" : null
}

Anyone knows a way / library to achieve this ?

As an example, Javascript's JSON does this fine:

Object {_id: "57f3e32c71285f85b46d6fb5", rate: Infinity, value: NaN}
JSON.stringify(tg)
"{"_id":"57f3e32c71285f85b46d6fb5","rate":null,"value":null}"

Solution

  • you can always make your own encoder (I stole this one from https://gist.github.com/pauloalem/6244976 )

    import json
    
    
    class FloatEncoder(json.JSONEncoder):
        def __init__(self, nan_str="null", **kwargs):
            super(FloatEncoder, self).__init__(**kwargs)
            self.nan_str = nan_str
    
        def iterencode(self, o, _one_shot=False):
            """Encode the given object and yield each string
            representation as available.
    
            For example::
    
                for chunk in JSONEncoder().iterencode(bigobject):
                    mysocket.write(chunk)
            """
            if self.check_circular:
                markers = {}
            else:
                markers = None
            if self.ensure_ascii:
                _encoder = json.encoder.encode_basestring_ascii
            else:
                _encoder = json.encoder.encode_basestring
            # if self.encoding != 'utf-8':
            #     def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
            #         if isinstance(o, str):
            #             o = o.decode(_encoding)
            #         return _orig_encoder(o)
    
            def floatstr(o, allow_nan=self.allow_nan, _repr=json.encoder.encode_basestring,
                    _inf=json.encoder.INFINITY, _neginf=-json.encoder.INFINITY,
                    nan_str=self.nan_str):
                # Check for specials.  Note that this type of test is processor
                # and/or platform-specific, so do tests which don't depend on the
                # internals.
    
                if o != o:
                    text = nan_str
                elif o == _inf:
                    text = 'Infinity'
                elif o == _neginf:
                    text = '-Infinity'
                else:
                    return _repr(o)
    
                if not allow_nan:
                    raise ValueError(
                        "Out of range float values are not JSON compliant: " +
                        repr(o))
    
                return text
    
            _iterencode = json.encoder._make_iterencode(
                    markers, self.default, _encoder, self.indent, floatstr,
                    self.key_separator, self.item_separator, self.sort_keys,
                    self.skipkeys, _one_shot)
            return _iterencode(o, 0)
    
    
    d = {"i_will_byte_you_in_the_ass": float("NaN")}
    print( json.dumps(d, cls=FloatEncoder) )