关于python:如何从JSON获取字符串对象而不是Unicode?

How to get string objects instead of Unicode from JSON?

我使用python 2从ASCII编码的文本文件解析JSON。

当用jsonsimplejson加载这些文件时,我的所有字符串值都转换为unicode对象,而不是字符串对象。问题是,我必须将数据与一些只接受字符串对象的库一起使用。我既不能更改库也不能更新库。

是否可以获取字符串对象而不是Unicode对象?

例子

1
2
3
4
5
6
7
8
>>> import json
>>> original_list = ['a', 'b']
>>> json_list = json.dumps(original_list)
>>> json_list
'["a","b"]'
>>> new_list = json.loads(json_list)
>>> new_list
[u'a', u'b']  # I want these to be of type `str`, not `unicode`

更新

这个问题在很久以前就被问到了,当时我一直在使用python 2。今天一个简单而干净的解决方案是使用最新版本的python,即python 3和forward。


虽然这里有一些很好的答案,但我最终还是使用pyyaml来解析我的JSON文件,因为它给出的键和值是str类型的字符串,而不是unicode类型。因为JSON是yaml的一个子集,所以它工作得很好:

1
2
3
4
5
6
7
8
9
10
>>> import json
>>> import yaml
>>> list_org = ['a', 'b']
>>> list_dump = json.dumps(list_org)
>>> list_dump
'["a","b"]'
>>> json.loads(list_dump)
[u'a', u'b']
>>> yaml.safe_load(list_dump)
['a', 'b']

笔记

但需要注意的是:

  • 我得到字符串对象,因为我的所有条目都是ASCII编码的。如果我使用Unicode编码的条目,我会将它们作为Unicode对象返回-没有转换!

  • 您应该(可能总是)使用pyyaml的safe_load函数;如果您使用它来加载JSON文件,那么您无论如何都不需要load函数的"附加功能"。

  • 如果您想要一个对1.2版本的规范有更多支持的yaml解析器(并且正确地解析非常低的数字),请尝试ruamel yaml:pip install ruamel.yamlimport ruamel.yaml as yaml是我在测试中所需要的。

转换

如前所述,没有转换!如果您不能确定只处理ASCII值(并且大部分时间都不能确定),最好使用转换函数:

我用过几次马克·阿米里的那个,效果很好,而且很容易使用。您也可以使用类似于object_hook的函数,因为它可能会提高大文件的性能。请看Mirec Miskuf关于这一点的回答。


没有使JSON模块函数返回字节字符串而不是Unicode字符串的内置选项。但是,这个简短而简单的递归函数将把任何解码后的JSON对象从使用Unicode字符串转换为UTF-8编码的字节字符串:

1
2
3
4
5
6
7
8
9
10
def byteify(input):
    if isinstance(input, dict):
        return {byteify(key): byteify(value)
                for key, value in input.iteritems()}
    elif isinstance(input, list):
        return [byteify(element) for element in input]
    elif isinstance(input, unicode):
        return input.encode('utf-8')
    else:
        return input

只需在您从json.loadjson.loads调用中获得的输出上调用它。

几个注意事项:

  • 为了支持python 2.6或更早版本,将return {byteify(key): byteify(value) for key, value in input.iteritems()}替换为return dict([(byteify(key), byteify(value)) for key, value in input.iteritems()]),因为直到python 2.7才支持字典理解。
  • 由于这个答案在整个解码对象中重复出现,因此它具有一些不良的性能特征,可以通过非常小心地使用object_hookobject_pairs_hook参数来避免。Mirec Miskuf的答案是迄今为止唯一能够正确实现这一点的答案,尽管其结果比我的方法要复杂得多。


使用object_hook的解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import json

def json_load_byteified(file_handle):
    return _byteify(
        json.load(file_handle, object_hook=_byteify),
        ignore_dicts=True
    )

def json_loads_byteified(json_text):
    return _byteify(
        json.loads(json_text, object_hook=_byteify),
        ignore_dicts=True
    )

def _byteify(data, ignore_dicts = False):
    # if this is a unicode string, return its string representation
    if isinstance(data, unicode):
        return data.encode('utf-8')
    # if this is a list of values, return list of byteified values
    if isinstance(data, list):
        return [ _byteify(item, ignore_dicts=True) for item in data ]
    # if this is a dictionary, return dictionary of byteified keys and values
    # but only if we haven't already byteified it
    if isinstance(data, dict) and not ignore_dicts:
        return {
            _byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True)
            for key, value in data.iteritems()
        }
    # if it's anything else, return it in its original form
    return data

示例用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> json_loads_byteified('{"Hello":"World"}')
{'Hello': 'World'}
>>> json_loads_byteified('"I am a top-level string"')
'I am a top-level string'
>>> json_loads_byteified('7')
7
>>> json_loads_byteified('["I am inside a list"]')
['I am inside a list']
>>> json_loads_byteified('[[[[[[[["I am inside a big nest of lists"]]]]]]]]')
[[[[[[[['I am inside a big nest of lists']]]]]]]]
>>> json_loads_byteified('{"foo":"bar","things": [7, {"qux":"baz","moo": {"cow": ["milk"]}}]}')
{'things': [7, {'qux': 'baz', 'moo': {'cow': ['milk']}}], 'foo': 'bar'}
>>> json_load_byteified(open('somefile.json'))
{'more json': 'from a file'}

这是如何工作的?我为什么要使用它?

马克·阿米里的功能比这些功能更短、更清晰,那么它们有什么意义呢?你为什么要用它们?

纯粹是为了表演。Mark的答案首先使用Unicode字符串完全解码JSON文本,然后通过整个解码值进行循环,以将所有字符串转换为字节字符串。这有一些不良影响:

  • 在内存中创建整个解码结构的副本
  • 如果您的JSON对象确实是深度嵌套的(500层或更多),那么您将达到Python的最大递归深度。

这个答案通过使用json.loadjson.loadsobject_hook参数来缓解这两个性能问题。来自文档:

object_hook is an optional function that will be called with the result of any object literal decoded (a dict). The return value of object_hook will be used instead of the dict. This feature can be used to implement custom decoders

由于词典在其他词典中嵌套了许多层次,因此在对它们进行解码时,可以将它们传递给object_hook,因此我们可以在这一点上忽略其中的任何字符串或列表,并避免以后进行深层递归。

Mark的答案不适合用作现有的object_hook,因为它会重复出现在嵌套字典中。我们防止在这个答案中使用ignore_dicts参数递归到_byteify,除非object_hook将一个新的dict传递到bytefy,否则会一直传递给它。ignore_dicts旗告诉_byteify不要理会dict旗,因为它们已经被证实了。

最后,我们对json.loadjson.loads返回的结果调用_byteifyjson_loads_byteified,以处理被解码的JSON文本在顶层没有dict的情况。


可以使用json.loadsobject_hook参数传入转换器。你不必在事后进行转换。json模块总是只传递object_hookdict,它将递归地传递嵌套dict,因此您不必自己重复使用嵌套dict。我不认为我会把unicode字符串转换成wells显示的数字。如果它是一个Unicode字符串,它在JSON文件中被引用为一个字符串,所以它应该是一个字符串(或者文件是坏的)。

另外,我会尽量避免在一个unicode对象上做类似于str(val)的事情。您应该使用带有有效编码的value.encode(encoding),这取决于您的外部lib的期望。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def _decode_list(data):
    rv = []
    for item in data:
        if isinstance(item, unicode):
            item = item.encode('utf-8')
        elif isinstance(item, list):
            item = _decode_list(item)
        elif isinstance(item, dict):
            item = _decode_dict(item)
        rv.append(item)
    return rv

def _decode_dict(data):
    rv = {}
    for key, value in data.iteritems():
        if isinstance(key, unicode):
            key = key.encode('utf-8')
        if isinstance(value, unicode):
            value = value.encode('utf-8')
        elif isinstance(value, list):
            value = _decode_list(value)
        elif isinstance(value, dict):
            value = _decode_dict(value)
        rv[key] = value
    return rv

obj = json.loads(s, object_hook=_decode_dict)


这是因为JSON在字符串对象和Unicode对象之间没有区别。它们都是javascript中的字符串。

我认为JSON返回Unicode对象是正确的。事实上,我不会接受更少的内容,因为javascript字符串实际上是unicode对象(即json(javascript)字符串可以存储任何类型的Unicode字符),所以在从json转换字符串时创建unicode对象是有意义的。普通字符串不适合,因为库必须猜测您想要的编码。

最好到处使用unicode字符串对象。所以,最好的选择是更新库,以便它们能够处理Unicode对象。

但是,如果您真的想要字节串,只需将结果编码为您选择的编码:

1
2
3
4
5
6
>>> nl = json.loads(js)
>>> nl
[u'a', u'b']
>>> nl = [s.encode('utf-8') for s in nl]
>>> nl
['a', 'b']


这里有一个简单的工作。

tl;dr-使用ast.literal_eval(),而不是json.loads()astjson都在标准库中。

虽然不是一个"完美"的答案,但如果您的计划是完全忽略Unicode,它会得到一个相当远的答案。在Python 2.7中

1
2
3
4
import json, ast
d = { 'field' : 'value' }
print"JSON Fail:", json.loads(json.dumps(d))
print"AST Win:", ast.literal_eval(json.dumps(d))

给予:

1
2
JSON Fail:  {u'field': u'value'}
AST Win: {'field': 'value'}

当某些对象实际上是Unicode字符串时,这会变得更加复杂。完整的答案很快就会变得复杂起来。


迈克·布伦南的答案很接近,但没有理由重新穿越整个结构。如果使用object_hook_pairs(python 2.7+)参数:

object_pairs_hook is an optional function that will be called with the result of any object literal decoded with an ordered list of pairs. The return value of object_pairs_hook will be used instead of the dict. This feature can be used to implement custom decoders that rely on the order that the key and value pairs are decoded (for example, collections.OrderedDict will remember the order of insertion). If object_hook is also defined, the object_pairs_hook takes priority.

有了它,您就可以将每个JSON对象交给您,这样您就可以在不需要递归的情况下进行解码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def deunicodify_hook(pairs):
    new_pairs = []
    for key, value in pairs:
        if isinstance(value, unicode):
            value = value.encode('utf-8')
        if isinstance(key, unicode):
            key = key.encode('utf-8')
        new_pairs.append((key, value))
    return dict(new_pairs)

In [52]: open('test.json').read()
Out[52]: '{"1":"hello","abc": [1, 2, 3],"def": {"hi":"mom
<div class="suo-content">[collapse title=""]<ul><li>这很整洁,似乎非常接近于应该得到绿色的对勾(令人钦佩的是,当更好的答案出现时,布鲁图斯已经慷慨地传递了这一点)。但是…为什么不在这个答案中显示的<wyn>deunicodify_hook</wyn>中正确处理列表呢?目前,您有一个<wyn>deunicodify_hook</wyn>的实现,它不迭代列表,也不分解其中的字符串和列表,因此显示的输出与钩子实际产生的输出不匹配。解决这个问题,这个答案会比我的好。</li><li>轻浮:我还建议用普通的cpython解释器来演示这个函数,而不是你在这里使用的那个解释器(我认为它是Ironpython)?cpython解释器对大多数Python用户来说更熟悉,在我看来,它更漂亮。</li><li>这对我不起作用,但我相信这是我所做的一些奇怪的事情…我正在将一个较大的JSON文档中的一个列表存储到一个文件中。无论我加载它时使用或不使用这个对象对挂钩,每个项目都会使用Unicode。织补。</li><li>@很有意义!由于只对对象调用<wyn>object_pairs_hook</wyn>,如果JSON文本在顶层有一个字符串列表,那么这个解决方案将失败。如果不对从<wyn>json.load</wyn>返回的对象调用某些函数,就无法修复此问题;<wyn>json.load</wyn>钩子都不能保证您能够处理每个字符串。我认为这是一个足够大的缺陷,我可以继续推荐我的解决方案,而不是使用钩子。</li><li>-1因为我刚刚意识到Mirec Miskuf已经发布了一个对象钩子答案,它既没有Mike Brennan方法的缺点(多次重复相同的字典),也没有这个方法的缺点(未能通过嵌套列表或顶级列表或字符串)。我不知道为什么他的回答几乎没有引起注意,而这一次-这是次-很快获得了选票。</li></ul>[/collapse]</div><hr><P>恐怕在simplejson库中无法自动实现这一点。</P><P>simplejson中的扫描仪和解码器设计用于生成Unicode文本。为此,库使用一个名为<wyn>c_scanstring</wyn>的函数(如果它可用,则用于speed),如果C版本不可用,则使用<wyn>py_scanstring</wyn>的函数。simplejson对可能包含文本的结构进行解码时,几乎每个例程都会多次调用<wyn>scanstring</wyn>函数。您要么在simplejson.decoder中对<wyn>scanstring</wyn>值进行monkeypatch,要么在<wyn>JSONDecoder</wyn>子类中提供几乎所有可能包含文本的实现。</P><P>然而,simplejson输出unicode的原因是json规范特别提到了"字符串是零个或多个unicode字符的集合"…假定对Unicode的支持是格式本身的一部分。simplejson的<wyn>scanstring</wyn>实现可以扫描和解释unicode转义(甚至错误检查多字节字符集表示形式的错误),因此它能够可靠地将值返回给您的唯一方法是unicode。</P><P>如果您有一个旧的库需要一个<wyn>str</wyn>,我建议您要么在解析后费力地搜索嵌套的数据结构(我承认这是您明确表示要避免的事情)。抱歉),或者将库包装在某种外观中,您可以在更细粒度的级别上按摩输入参数。如果数据结构确实是深度嵌套的,那么第二种方法可能比第一种方法更易于管理。</P><hr><P>正如Mark(Amery)正确指出的那样:在JSON转储上使用pyyaml的反序列化程序只在您只有ASCII的情况下才有效。至少是开箱即用。</P><P>关于Pyyaml方法的两个快速评论:</P><li><P>切勿对来自现场的数据使用yaml.load。它是一个功能!!)执行隐藏在结构中的任意代码。</P></li><li><P>您也可以通过以下方式使其适用于非ASCII:</P>[cc lang="python"]def to_utf8(loader, node):
    return loader.construct_scalar(node).encode('
utf-8')
yaml.add_constructor(u'
tag:yaml.org,2002:str', to_utf8)

但从性能上看,这与马克·阿米里的答案没有什么区别:

将一些深度嵌套的示例dict放到这两个方法中,我得到了这个结果(dt[j]=json.loads的时间增量(json.dumps(m)):

1
2
     dt[yaml.safe_load(json.dumps(m))] =~ 100 * dt[j]
     dt[byteify recursion(Mark Amery)] =~   5 * dt[j]

因此,反序列化包括完全遍历树和编码,完全符合JSON基于C的实现的数量级。我发现这非常快,而且它也比深嵌套结构的yaml负载更坚固。并且不太容易出现安全错误,查看yaml.load。

=>虽然我希望有一个指向仅基于C的转换器的指针,但是byteify函数应该是默认答案。

如果JSON结构来自包含用户输入的字段,则这一点尤其适用。因为这样,您可能需要遍历您的结构——独立于所需的内部数据结构("unicode三明治"或字节字符串)。

为什么?

Unicode标准化。对于不知情者:服用止痛药并阅读本书。

所以使用byteify递归,你可以用一块石头杀死两只鸟:

  • 从嵌套的JSON转储中获取字节集
  • 让用户输入值正常化,这样你就可以在你的存储中找到这些东西。
  • 在我的测试中,结果发现用unicodedata.normalize("nfc",input).encode("utf-8")替换input.encode("utf-8")比不使用nfc更快,但这很大程度上取决于样本数据。


    gotcha是simplejsonjson是两个不同的模块,至少在处理unicode的方式上是这样的。您在py 2.6+中有json,这为您提供了unicode值,而simplejson返回字符串对象。只需在您的环境中尝试轻松安装simplejson,看看是否有效。它为我做的。


    只需使用pickle而不是json进行转储和加载,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
        import json
        import pickle

        d = { 'field1': 'value1', 'field2': 2, }

        json.dump(d,open("testjson.txt","w"))

        print json.load(open("testjson.txt","r"))

        pickle.dump(d,open("testpickle.txt","w"))

        print pickle.load(open("testpickle.txt","r"))

    它产生的输出是(字符串和整数处理正确):

    1
    2
        {u'field2': 2, u'field1': u'value1'}
        {'field2': 2, 'field1': 'value1'}


    所以,我也遇到了同样的问题。猜猜谷歌的第一个结果是什么。

    因为我需要将所有数据传递给Pygtk,所以Unicode字符串对我也不是很有用。所以我有另一个递归转换方法。它实际上也是类型安全JSON转换所必需的——json.dump()可以在任何非文本(如python对象)上提供帮助。但不转换dict索引。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # removes any objects, turns unicode back into str
    def filter_data(obj):
            if type(obj) in (int, float, str, bool):
                    return obj
            elif type(obj) == unicode:
                    return str(obj)
            elif type(obj) in (list, tuple, set):
                    obj = list(obj)
                    for i,v in enumerate(obj):
                            obj[i] = filter_data(v)
            elif type(obj) == dict:
                    for i,v in obj.iteritems():
                            obj[i] = filter_data(v)
            else:
                    print"invalid object in data, converting to string"
                    obj = str(obj)
            return obj


    使用钩子支持python2&3(来自https://stackoverflow.com/a/3357117/558397)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import requests
    import six
    from six import iteritems

    requests.packages.urllib3.disable_warnings()  # @UndefinedVariable
    r = requests.get("http://echo.jsontest.com/key/value/one/two/three", verify=False)

    def _byteify(data):
        # if this is a unicode string, return its string representation
        if isinstance(data, six.string_types):
            return str(data.encode('utf-8').decode())

        # if this is a list of values, return list of byteified values
        if isinstance(data, list):
            return [ _byteify(item) for item in data ]

        # if this is a dictionary, return dictionary of byteified keys and values
        # but only if we haven't already byteified it
        if isinstance(data, dict):
            return {
                _byteify(key): _byteify(value) for key, value in iteritems(data)
            }
        # if it's anything else, return it in its original form
        return data

    w = r.json(object_hook=_byteify)
    print(w)

    返回:

    1
     {'three': '', 'key': 'value', 'one': 'two'}

    对于Python3.6,有时我仍然会遇到这个问题。例如,当从RESTAPI获取响应并将响应文本加载到JSON时,我仍然会得到Unicode字符串。使用json.dumps()找到了一个简单的解决方案。

    1
    2
    response_message = json.loads(json.dumps(response.text))
    print(response_message)


    我有一个JSON听写作为字符串。键和值是Unicode对象,如下例所示:

    1
    myStringDict ="{u'key':u'value'}"

    我可以使用上面建议的byteify函数,方法是使用ast.literal_eval(myStringDict)将字符串转换为dict对象。


    下面是用C编写的递归编码器:https://github.com/axiros/nested_编码

    与json.loads相比,"平均"结构的性能开销约为10%。

    1
    2
    3
    4
    python speed.py                                                                                            
      json loads            [0.16sec]: {u'a': [{u'b': [[1, 2, [u'\xd6ster..
      json loads + encoding [0.18sec]: {'
    a': [{'b': [[1, 2, ['\xc3\x96ster.
      time overhead in percent: 9%

    使用此测试结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    import json, nested_encode, time

    s ="""
    {
     "firstName":"Jos\\u0301",
     "lastName":"Smith",
     "isAlive": true,
     "age": 25,
     "address": {
       "streetAddress":"21 2nd Street",
       "city":"\\u00d6sterreich",
       "state":"NY",
       "postalCode":"10021-3100"
      },
     "phoneNumbers": [
        {
         "type":"home",
         "number":"212 555-1234"
        },
        {
         "type":"office",
         "number":"646 555-4567"
        }
      ],
     "children": [],
     "spouse": null,
     "a": [{"b": [[1, 2, ["\\u00d6sterreich"]]]}]
    }
    """



    t1 = time.time()
    for i in xrange(10000):
        u = json.loads(s)
    dt_json = time.time() - t1

    t1 = time.time()
    for i in xrange(10000):
        b = nested_encode.encode_nested(json.loads(s))
    dt_json_enc = time.time() - t1

    print"json loads            [%.2fsec]: %s..." % (dt_json, str(u)[:20])
    print"json loads + encoding [%.2fsec]: %s..." % (dt_json_enc, str(b)[:20])

    print"time overhead in percent: %i%%"  % (100 * (dt_json_enc - dt_json)/dt_json)

    Check out this answer to a similar question like this which states that

    The u- prefix just means that you have a Unicode string. When you really use the string, it won't appear in your data. Don't be thrown by the printed output.

    For example, try this:

    1
    print mail_accounts[0]["i"]

    你不会看到美国。


    这是游戏后期,但我建立了这个递归的施法者。它能满足我的需要,我认为它相对完整。这可能对你有帮助。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    def _parseJSON(self, obj):
        newobj = {}

        for key, value in obj.iteritems():
            key = str(key)

            if isinstance(value, dict):
                newobj[key] = self._parseJSON(value)
            elif isinstance(value, list):
                if key not in newobj:
                    newobj[key] = []
                    for i in value:
                        newobj[key].append(self._parseJSON(i))
            elif isinstance(value, unicode):
                val = str(value)
                if val.isdigit():
                    val = int(val)
                else:
                    try:
                        val = float(val)
                    except ValueError:
                        val = str(val)
                newobj[key] = val

        return newobj

    只需像这样传递一个JSON对象:

    1
    2
    obj = json.loads(content, parse_float=float, parse_int=int)
    obj = _parseJSON(obj)

    我将它作为类的私有成员,但您可以根据需要重新调整方法的用途。


    我重写了Wells的_parse_json()来处理JSON对象本身是数组的情况(我的用例)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def _parseJSON(self, obj):
        if isinstance(obj, dict):
            newobj = {}
            for key, value in obj.iteritems():
                key = str(key)
                newobj[key] = self._parseJSON(value)
        elif isinstance(obj, list):
            newobj = []
            for value in obj:
                newobj.append(self._parseJSON(value))
        elif isinstance(obj, unicode):
            newobj = str(obj)
        else:
            newobj = obj
        return newobj


    我修改了马克·阿米里的答案中的代码,特别是为了去掉isinstance,使之适合鸭子打字。

    手动进行编码,禁用ensure_ascii。用于json.dump的python文档说

    If ensure_ascii is True (the default), all non-ASCII characters in the output are escaped with \uXXXX sequences

    免责声明:在教科书中我使用匈牙利语。一些与匈牙利语相关的显著字符编码是:cp852--在DOS中使用的IBM/OEM编码(有时被错误地称为ASCII,我认为这取决于代码页设置),cp1250--在Windows中使用(有时被称为ANSI,取决于区域设置),和iso-8859-2,有时在HTTP服务器上使用。测试文本Tüskéshátú kígyób?v?l?来源于koltai l_szl_(本地人名形式),来自维基百科。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    # coding: utf-8
    """
    This file should be encoded correctly with utf-8.
    """

    import json

    def encode_items(input, encoding='utf-8'):
        u"""original from: https://stackoverflow.com/a/13101776/611007
        adapted by SO/u/611007 (20150623)
        >>>
        >>> ## run this with `python -m doctest <this file>.py` from command line
        >>>
        >>> txt = u"Tüskéshátú kígyób?v?l?"
        >>> txt2 = u"T\\u00fcsk\\u00e9sh\\u00e1t\\u00fa k\\u00edgy\\u00f3b\\u0171v\\u00f6l\\u0151"
        >>> txt3 = u"uúuutifu"
        >>> txt4 = b'u\\xfauutifu'
        >>> # txt4 shouldn't be 'u\\xc3\\xbauutifu', string content needs double backslash for doctest:
        >>> assert u'\\u0102' not in b'u\\xfauutifu'.decode('cp1250')
        >>> txt4u = txt4.decode('cp1250')
        >>> assert txt4u == u'u\\xfauutifu', repr(txt4u)
        >>> txt5 = b"u\\xc3\\xbauutifu"
        >>> txt5u = txt5.decode('utf-8')
        >>> txt6 = u"u\\u251c\\u2551uutifu"
        >>> there_and_back_again = lambda t: encode_items(t, encoding='utf-8').decode('utf-8')
        >>> assert txt == there_and_back_again(txt)
        >>> assert txt == there_and_back_again(txt2)
        >>> assert txt3 == there_and_back_again(txt3)
        >>> assert txt3.encode('cp852') == there_and_back_again(txt4u).encode('cp852')
        >>> assert txt3 == txt4u,(txt3,txt4u)
        >>> assert txt3 == there_and_back_again(txt5)
        >>> assert txt3 == there_and_back_again(txt5u)
        >>> assert txt3 == there_and_back_again(txt4u)
        >>> assert txt3.encode('cp1250') == encode_items(txt4, encoding='utf-8')
        >>> assert txt3.encode('utf-8') == encode_items(txt5, encoding='utf-8')
        >>> assert txt2.encode('utf-8') == encode_items(txt, encoding='utf-8')
        >>> assert {'a':txt2.encode('utf-8')} == encode_items({'a':txt}, encoding='utf-8')
        >>> assert [txt2.encode('utf-8')] == encode_items([txt], encoding='utf-8')
        >>> assert [[txt2.encode('utf-8')]] == encode_items([[txt]], encoding='utf-8')
        >>> assert [{'a':txt2.encode('utf-8')}] == encode_items([{'a':txt}], encoding='utf-8')
        >>> assert {'b':{'a':txt2.encode('utf-8')}} == encode_items({'b':{'a':txt}}, encoding='utf-8')
       """

        try:
            input.iteritems
            return {encode_items(k): encode_items(v) for (k,v) in input.iteritems()}
        except AttributeError:
            if isinstance(input, unicode):
                return input.encode(encoding)
            elif isinstance(input, str):
                return input
            try:
                iter(input)
                return [encode_items(e) for e in input]
            except TypeError:
                return input

    def alt_dumps(obj, **kwargs):
       """
        >>> alt_dumps({'a': u"T\\u00fcsk\\u00e9sh\\u00e1t\\u00fa k\\u00edgy\\u00f3b\\u0171v\\u00f6l\\u0151"})
        '{"a":"T\\xc3\\xbcsk\\xc3\\xa9sh\\xc3\\xa1t\\xc3\\xba k\\xc3\\xadgy\\xc3\\xb3b\\xc5\\xb1v\\xc3\\xb6l\\xc5\\x91"}'
       """

        if 'ensure_ascii' in kwargs:
            del kwargs['ensure_ascii']
        return json.dumps(encode_items(obj), ensure_ascii=False, **kwargs)

    我还想强调一下Jarret Hardie的答案,它引用了JSON规范,引用了:

    A string is a collection of zero or more Unicode characters

    在我的用例中,我有JSON文件。它们是utf-8编码的文件。ensure_ascii导致正确转义但不可读的JSON文件,这就是为什么我调整了mark amery的答案以满足我的需要。

    doctest并不是特别周到,但我共享代码,希望它对某些人有用。


    我也遇到了这个问题,不得不处理JSON,我想出了一个小循环,将Unicode键转换为字符串。(GAE上的simplejson不返回字符串键。)

    obj是从json解码的对象:

    1
    2
    3
    4
    5
    6
    if NAME_CLASS_MAP.has_key(cls):
        kwargs = {}
        for i in obj.keys():
            kwargs[str(i)] = obj[i]
        o = NAME_CLASS_MAP[cls](**kwargs)
        o.save()

    我把kwargs传递给gae应用程序的构造函数(它不喜欢**kwargs中的unicode键)。

    不是像井的解决方案那样强大,而是小得多。