настроить выход simplejson - PullRequest
       13

настроить выход simplejson

2 голосов
/ 22 марта 2011

Я конвертирую Python dicts с simplejson, но я бы хотел настроить вывод для некоторых определенных ключей.

например, я бы хотел, чтобы ключи callback и scope всегда отображались с без окружающих кавычек , чтобы JavaScript мог интерпретировать данные и не читать их как строку.

пример желаемого вывода:

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

Обратите внимание, что клавиши callback и scope не имеют окружающих кавычек в своих значениях.

Я безуспешно пытался создать пользовательский класс и подкласс JSONencoder.

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)

Я знаю, что полученный JSON может быть неверным в рекомендациях JSON, но это важно для некоторых приложений JS.

Я пробовал некоторые обходные пути для регулярных выражений, но он становится сложным для многоканальных и встроенных функций с данными внутри.

Спасибо!

Ответы [ 2 ]

1 голос
/ 04 июля 2013

Я немного изменил проблему и предположил, что мой клиент закодирует функцию в виде словаря {"__js_func__": "function() { return 10; }"}, поскольку я точно не знаю, как будут вызываться мои функции.

По сути, я написал собственный декодер, который с помощью ключа __js_func__ превращает диктовку в объект JSFunction, а затем исправляет обезьяны функцией encode_basestring_ascii, чтобы не добавлять кавычки для такого типа объектов. Мой объект JSFunction расширяет str, что означает, что encode_basestring_ascii вызывается как для обычных строк, так и для объектов JSFunction.

import json
import json.encoder

class JSFunction(str):
    def __repr__(self):
        return '<JSFunction: self>'

    @staticmethod
    def decode_js_func(dct):
        """Turns any dictionary that contains a __js_func__ key into a JSFunction object.                                            

        Used when loads()'ing the json sent to us by the webserver.                                                                  
        """
        if '__js_func__' in dct:
            return JSFunction(dct['__js_func__'])
        return dct

    @staticmethod
    def encode_basestring(s):
        """A function we use to monkeypatch json.encoder.encode_basestring_ascii that dumps JSFunction objects without quotes."""
        if isinstance(s, JSFunction):
            return str(s)
        else:
            return _original_encode_basestring_ascii(s)

_original_encode_basestring_ascii = json.encoder.encode_basestring_ascii
json.encoder.encode_basestring_ascii = JSFunction.encode_basestring

Итак, когда вы запускаете код с вашим примером:

>>> data = '{"name": "Julien", "callback": {"__js_func__": "function() { return 10; }"}}'
>>> json.loads(data, object_hook=JSFunction.decode_js_func)
{u'callback': <JSFunction: self>, u'name': u'Julien'}

И чтобы превратить это в красиво сериализованную строку json, просто json.dumps () it:

>>> json.dumps(json.loads(data, object_hook=JSFunction.decode_js_func))
'{"callback": function() { return 10; }, "name": "Julien"}'

Теперь это становится довольно запутанным, так как мы исправили поведение базового модуля json (таким образом, это повлияет на все приложение), но оно выполняет свою работу.

Я бы хотел увидеть более чистый способ сделать это!

1 голос
/ 22 марта 2011

Мне удалось изменить код JSON

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))

РЕДАКТИРОВАНИЕ: точность кода, который я изменил

из json.encode Я взял функцию _make_iterencode

добавив что-то вроде

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

в трех местах

из JsonEncoder Я взял метод iterencode но я просто заставил _iterencode быть моей пользовательской функцией _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)

Надеюсь, это понятно

...