Search code examples
pythonjsonsimplejson

customise simplejson output


I convert python dicts with simplejson, but I'd like to customise the output for some defined keys.

for example, I'd like the keys callback and scope to be always rendered with no surrounding quotes so the javascript can interpret the data and not read it as a string.

example desired output :

"data":{
    "name":"Julien"
    ,"callback":function() {alert('hello, world');}
    ,"params":{
       "target":"div2"
       ,"scope":this
     }
}

Notice the callback and scope keys have no surrounding quotes in their values.

I've tried create a custom class and subclass JSONencoder with no luck.

class JsonSpecialKey(object):
    def __init__(self, data):
        self.data = data

class JsonSpecialEncoder(simplejson.JSONEncoder):
     def default(self, obj):
        if isinstance (obj, JsonSpecialKey):
            # how to remove quotes ??
            return obj.data
        return simplejson.JSONEncoder.default(self, obj)

d = {'testKey':JsonSpecialKey('function() {alert(123);}')}
print simplejson.dumps(d, cls=JsonSpecialEncoder, ensure_ascii=False, indent=4)

I know the resulting JSON may be invalid in the JSON recommendations but it is important to some JS applications.

I've tried some regex workarounds but it's getting complex for multilines and inline functions with data inside.

Thank you !


Solution

  • I succeed by modifying json code

    import json
    from json.encoder import encode_basestring_ascii ,encode_basestring,FLOAT_REPR,INFINITY,c_make_encoder
    class JsonSpecialKey(object):
        def __init__(self, data):
            self.data = data
    def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
            _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
            ## HACK: hand-optimized bytecode; turn globals into locals
            ValueError=ValueError,
            dict=dict,
            float=float,
            id=id,
            int=int,
            isinstance=isinstance,
            list=list,
            str=str,
            tuple=tuple,
        ):
    
        if _indent is not None and not isinstance(_indent, str):
            _indent = ' ' * _indent
    
        def _iterencode_list(lst, _current_indent_level):
            if not lst:
                yield '[]'
                return
            if markers is not None:
                markerid = id(lst)
                if markerid in markers:
                    raise ValueError("Circular reference detected")
                markers[markerid] = lst
            buf = '['
            if _indent is not None:
                _current_indent_level += 1
                newline_indent = '\n' + _indent * _current_indent_level
                separator = _item_separator + newline_indent
                buf += newline_indent
            else:
                newline_indent = None
                separator = _item_separator
            first = True
            for value in lst:
                if first:
                    first = False
                else:
                    buf = separator
                if isinstance(value, str):
                    yield buf + _encoder(value)
                elif value is None:
                    yield buf + 'null'
                elif value is True:
                    yield buf + 'true'
                elif value is False:
                    yield buf + 'false'
                elif isinstance(value, int):
                    yield buf + str(value)
                elif isinstance(value, float):
                    yield buf + _floatstr(value)
                elif isinstance(value, JsonSpecialKey):
                    yield buf + value.data
    
                else:
                    yield buf
                    if isinstance(value, (list, tuple)):
                        chunks = _iterencode_list(value, _current_indent_level)
                    elif isinstance(value, dict):
                        chunks = _iterencode_dict(value, _current_indent_level)
                    else:
                        chunks = _iterencode(value, _current_indent_level)
                    for chunk in chunks:
                        yield chunk
            if newline_indent is not None:
                _current_indent_level -= 1
                yield '\n' + _indent * _current_indent_level
            yield ']'
            if markers is not None:
                del markers[markerid]
    
        def _iterencode_dict(dct, _current_indent_level):
            if not dct:
                yield '{}'
                return
            if markers is not None:
                markerid = id(dct)
                if markerid in markers:
                    raise ValueError("Circular reference detected")
                markers[markerid] = dct
            yield '{'
            if _indent is not None:
                _current_indent_level += 1
                newline_indent = '\n' + _indent * _current_indent_level
                item_separator = _item_separator + newline_indent
                yield newline_indent
            else:
                newline_indent = None
                item_separator = _item_separator
            first = True
            if _sort_keys:
                items = sorted(dct.items(), key=lambda kv: kv[0])
            else:
                items = dct.items()
            for key, value in items:
                if isinstance(key, str):
                    pass
                # JavaScript is weakly typed for these, so it makes sense to
                # also allow them.  Many encoders seem to do something like this.
                elif isinstance(key, float):
                    key = _floatstr(key)
                elif key is True:
                    key = 'true'
                elif key is False:
                    key = 'false'
                elif key is None:
                    key = 'null'
                elif isinstance(key, int):
                    key = str(key)
                elif _skipkeys:
                    continue
                else:
                    raise TypeError("key " + repr(key) + " is not a string")
                if first:
                    first = False
                else:
                    yield item_separator
                yield _encoder(key)
                yield _key_separator
                if isinstance(value, str):
                    yield _encoder(value)
                elif value is None:
                    yield 'null'
                elif value is True:
                    yield 'true'
                elif value is False:
                    yield 'false'
                elif isinstance(value, int):
                    yield str(value)
                elif isinstance(value, float):
                    yield _floatstr(value)
                elif isinstance(value, JsonSpecialKey):
                    yield value.data
                else:
                    if isinstance(value, (list, tuple)):
                        chunks = _iterencode_list(value, _current_indent_level)
                    elif isinstance(value, dict):
                        chunks = _iterencode_dict(value, _current_indent_level)
                    else:
                        chunks = _iterencode(value, _current_indent_level)
                    for chunk in chunks:
                        yield chunk
            if newline_indent is not None:
                _current_indent_level -= 1
                yield '\n' + _indent * _current_indent_level
            yield '}'
            if markers is not None:
                del markers[markerid]
    
        def _iterencode(o, _current_indent_level):
            if isinstance(o, str):
                yield _encoder(o)
            elif o is None:
                yield 'null'
            elif o is True:
                yield 'true'
            elif o is False:
                yield 'false'
            elif isinstance(o, int):
                yield str(o)
            elif isinstance(o, float):
                yield _floatstr(o)
            elif isinstance(o, JsonSpecialKey):
                yield o.data
            elif isinstance(o, (list, tuple)):
                for chunk in _iterencode_list(o, _current_indent_level):
                    yield chunk
            elif isinstance(o, dict):
                for chunk in _iterencode_dict(o, _current_indent_level):
                    yield chunk
            else:
                if markers is not None:
                    markerid = id(o)
                    if markerid in markers:
                        raise ValueError("Circular reference detected")
                    markers[markerid] = o
                o = _default(o)
                for chunk in _iterencode(o, _current_indent_level):
                    yield chunk
                if markers is not None:
                    del markers[markerid]
        return _iterencode
    class JsonSpecialEncoder(json.JSONEncoder):
    
    
         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 = encode_basestring_ascii
            else:
                _encoder = encode_basestring
            def floatstr(o, allow_nan=self.allow_nan,
                    _repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY):
                # 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'
                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 = _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 = {'testKey':JsonSpecialKey('function() {alert(123);}')}
    print (json.dumps(d, cls=JsonSpecialEncoder, ensure_ascii=False, indent=4))
    

    EDITED:precision of the code I modified

    from json.encode I took the function _make_iterencode

    adding something like

           elif isinstance(value, JsonSpecialKey):
                yield buf + value.data
    

    at three places

    from JsonEncoder I took the method iterencode but I just forced the _iterencode to be my custom function _make_iterencode

     _iterencode = _make_iterencode(
            markers, self.default, _encoder, self.indent, floatstr,
            self.key_separator, self.item_separator, self.sort_keys,
            self.skipkeys, _one_shot)
    

    I hope it's clear