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

时间:2009-06-05 16:32:17

标签: python json serialization unicode python-2.x

我正在使用 Python 2 来解析 ASCII编码文本文件中的JSON。

使用jsonsimplejson加载这些文件时,我的所有字符串值都会转换为Unicode对象而不是字符串对象。问题是,我必须使用一些只接受字符串对象的库的数据。我无法更改库也无法更新它们。

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

实施例

>>> 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 并转发。

21 个答案:

答案 0 :(得分:174)

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

>>> 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函数的“额外功能”。

  • 如果您希望YAML解析器对规范的1.2版本(以及correctly parses very low numbers)有更多支持,请尝试Ruamel YAMLpip install ruamel.yamlimport ruamel.yaml as yaml我在测试中所需要的一切。

转换

如上所述,没有转换!如果你不能确定只处理ASCII值(并且你大多数时候都不能确定),那么最好使用转换函数

我现在使用过Mark Amery次的那个,效果很好而且非常容易使用。您也可以使用与object_hook类似的功能,因为它可能会提升大文件的性能。请参阅稍微复杂一点的answer from Mirec Miskuf

答案 1 :(得分:139)

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

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's answer是唯一一个能够正确解决问题的人,尽管如此,它比我的方法复杂得多。

答案 2 :(得分:91)

object_hook

的解决方案
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

使用示例:

>>> 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 Amery's function比这些更短更清晰,那么它们有什么意义呢?你为什么要使用它们?

纯粹为表现。 Mark的回答首先使用unicode字符串完全解码JSON文本,然后通过整个解码值进行递归,将所有字符串转换为字节字符串。这有几个不良影响:

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

此答案通过使用object_hookjson.load的{​​{1}}参数来缓解这两个性能问题。来自the docs

  

json.loads是一个可选函数,将使用任何对象文字解码的结果调用(object_hook)。将使用object_hook的返回值而不是dict。此功能可用于实现自定义解码器

由于字典嵌套在其他字典深处的许多级别被传递给dict ,因为它们已被解码,我们可以在那时对其中的任何字符串或列表进行字节化,并避免需要后来深度递归。

Mark的答案不适合用作object_hook,因为它会递归到嵌套的词典中。我们使用object_hook参数ignore_dicts来阻止此答案中的递归,_byteify传递给它{em>除时object_hook传递新dict ignore_dicts 1}}字节化。 _byteify标记告诉dict忽略json_load_byteified,因为它们已被字节化。

最后,我们对json_loads_byteified_byteify的实施调用ignore_dicts=Truejson.load)对json.loadsdict返回的结果处理正在解码的JSON文本在顶层没有data.table的情况。

答案 3 :(得分:73)

您可以使用json.loadsobject_hook参数传入转换器。事后你不必进行转换。 json模块将始终仅传递object_hook dicts,并且它将递归传递嵌套的dicts,因此您不必自己递归到嵌套的dicts。我不认为我会将unicode字符串转换为Wells节目等数字。如果它是一个unicode字符串,它在JSON文件中被引用为一个字符串,所以它应该是一个字符串(或者文件很糟糕)。

另外,我会尽量避免在str(val)对象上执行unicode之类的操作。您应该将value.encode(encoding)与有效编码一起使用,具体取决于外部lib期望的内容。

所以,例如:

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)

答案 4 :(得分:37)

那是因为json在字符串对象和unicode对象之间没有区别。它们都是javascript中的所有字符串。

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

最好在任何地方使用unicode字符串对象。因此,最好的选择是更新库,以便它们可以处理unicode对象。

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

>>> nl = json.loads(js)
>>> nl
[u'a', u'b']
>>> nl = [s.encode('utf-8') for s in nl]
>>> nl
['a', 'b']

答案 5 :(得分:14)

有一种简单的解决方法。

TL; DR - 使用ast.literal_eval()代替json.loads()astjson都在标准库中。

虽然不是一个完美的'回答,如果您的计划完全忽略Unicode,它会得到一个很好的结果。在Python 2.7中

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

给出:

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

当某些对象真的是Unicode字符串时,这会变得更加毛茸茸。完整的答案会很快变成毛茸茸的。

答案 6 :(得分:11)

Mike Brennan's answer已接近,但没有理由重新遍历整个结构。如果您使用object_hook_pairs(Python 2.7+)参数:

  

object_pairs_hook 是一个可选函数,将使用对有序列表对解码的任何对象文字的结果进行调用。将使用 object_pairs_hook 的返回值代替dict。此功能可用于实现依赖于键和值对解码顺序的自定义解码器(例如,collections.OrderedDict将记住插入顺序)。如果还定义了 object_hook ,则 object_pairs_hook 优先。

有了它,你就会得到每个JSON对象,所以你可以在不需要递归的情况下进行解码:

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"}, "boo": [1, "hi", "moo", {"5": "some"}]}'                                        

In [53]: json.load(open('test.json'))
Out[53]: 
{u'1': u'hello',
 u'abc': [1, 2, 3],
 u'boo': [1, u'hi', u'moo', {u'5': u'some'}],
 u'def': {u'hi': u'mom'}}

In [54]: json.load(open('test.json'), object_pairs_hook=deunicodify_hook)
Out[54]: 
{'1': 'hello',
 'abc': [1, 2, 3],
 'boo': [1, 'hi', 'moo', {'5': 'some'}],
 'def': {'hi': 'mom'}}

请注意,我永远不必递归地调用钩子,因为当你使用object_pairs_hook时,每个对象都会被传递给钩子。您必须关心列表,但正如您所看到的,列表中的对象将被正确转换,您无需递归即可实现。

编辑:一位同事指出Python2.6没有object_hook_pairs。您仍然可以通过进行非常小的更改来使用Python2.6。在上面的钩子中,改变:

for key, value in pairs:

for key, value in pairs.iteritems():

然后使用object_hook代替object_pairs_hook

In [66]: json.load(open('test.json'), object_hook=deunicodify_hook)
Out[66]: 
{'1': 'hello',
 'abc': [1, 2, 3],
 'boo': [1, 'hi', 'moo', {'5': 'some'}],
 'def': {'hi': 'mom'}}

使用object_pairs_hook会导致为JSON对象中的每个对象实例化一个较少的字典,如果您正在解析一个巨大的文档,那么它可能值得一试。

答案 7 :(得分:9)

我担心在simplejson库中无法自动实现这一点。

simplejson中的扫描仪和解码器旨在生成unicode文本。为此,库使用名为c_scanstring的函数(如果可用,速度为快),如果C版本不可用,则使用py_scanstring。几乎所有simplejson用于解码可能包含文本的结构的例程都会多次调用scanstring函数。您必须在simplejson.decoder或子类scanstring中对JSONDecoder值进行monkeypatch,并提供几乎所有可能包含文本的实现。

然而,simplejson输出unicode的原因是json spec特别提到“字符串是零个或多个Unicode字符的集合”......对unicode的支持被假定为格式本身的一部分。 Simplejson的scanstring实现甚至扫描和解释unicode转义(甚至错误检查格式错误的多字节字符集表示),因此它可以可靠地将值返回给你的唯一方法是unicode。

如果您有一个需要str的老化库,我建议您在解析后仔细搜索嵌套数据结构(我承认您明确表示要避免...抱歉),或者或许将您的库包装在某种外观中,您可以在更精细的层面上按下输入参数。如果您的数据结构确实是嵌套的,那么第二种方法可能比第一种方法更易于管理。

答案 8 :(得分:4)

正如Mark(Amery)正确指出:在json转储上使用 PyYaml 的反序列化器仅在您只有ASCII时才有效。至少开箱即用。

关于PyYaml方法的两个快速评论:

  1. NEVER对来自该字段的数据使用yaml.load。它是yaml的一个特性(!),用于执行隐藏在结构中的任意代码。

  2. 可以通过以下方式使其适用于非ASCII:

    def to_utf8(loader, node):
        return loader.construct_scalar(node).encode('utf-8')
    yaml.add_constructor(u'tag:yaml.org,2002:str', to_utf8)
    
  3. 但表现明智,与马克·阿梅里的答案无法比较:

    将两个深层嵌套的样本序列扔到两个方法上,我得到了这个(使用dt [j] = json.loads的时间增量(json.dumps(m))):

         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 sandwich'或仅字节字符串)。

    为什么?

    Unicode 规范化。对于不知情的人:拿一个止痛药并阅读this

    因此,使用byteify递归可以一举两得:

    1. 从嵌套的json转储中获取您的字节串
    2. 将用户输入值标准化,以便在存储中找到内容。
    3. 在我的测试中,结果发现用unicodedata.normalize('NFC',输入).encode('utf-8')替换input.encode('utf-8')比使用w / o更快NFC - 但这很大程度上取决于我猜测的样本数据。

答案 9 :(得分:3)

问题是simplejsonjson是两个不同的模块,至少以它们处理unicode的方式。你在py 2.6+中有json,这给你unicode值,而simplejson返回字符串对象。只需在您的环境中尝试easy_install-ing simplejson,看看是否有效。它确实适合我。

答案 10 :(得分:2)

只需使用pickle代替json进行转储和加载,如下所示:

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

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

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

答案 11 :(得分:1)

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

因为我需要将所有数据传递给PyGTK,所以unicode字符串对我来说也不是很有用。所以我有另一种递归转换方法。它实际上也需要类型安全的JSON转换 - json.dump()会保留任何非文字,比如Python对象。但是不会转换dict索引。

# 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

答案 12 :(得分:1)

使用hook(来自https://stackoverflow.com/a/33571117/558397

支持Python2& 3
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)

返回:

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

答案 13 :(得分:0)

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

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

答案 14 :(得分:0)

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

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

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

答案 15 :(得分:0)

这是一个用C编写的递归编码器: https://github.com/axiros/nested_encode

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

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%

使用此测试结构:

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)

答案 16 :(得分:0)

这是游戏的后期,但我建造了这个递归施法者。它适用于我的需求,我认为它相对完整。它可能对你有帮助。

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对象:

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

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

答案 17 :(得分:0)

查看this类似问题的回答,其中说明

u-前缀只表示您有一个Unicode字符串。当您真正使用该字符串时,它不会出现在您的数据中。不要被打印输出抛出。

例如,试试这个:

print mail_accounts[0]["i"]

你不会看到你。

答案 18 :(得分:0)

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

createBackgroundUrl(imageName) {
    return `url(assets/weatherBG/${imageName})` 
}

答案 19 :(得分:-1)

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

obj是从JSON解码的对象:

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应用程序的构造函数(它不喜欢unicode中的**kwargs个键)

不像Wells的解决方案那么强大,但要小得多。

答案 20 :(得分:-1)

我已经调整了answer Mark Amery中的代码,特别是为了摆脱isinstance鸭子打字的优点。

手动完成编码,并禁用ensure_asciijson.dump的python文档说明了

  

如果ensure_ascii为True(默认值),则输出中的所有非ASCII字符都使用\ uXXXX序列进行转义

免责声明:在doctest中我使用匈牙利语。一些值得注意的与匈牙利相关的字符编码是:cp852使用的IBM / OEM编码。在DOS中(有时称为 ascii ,我认为错误,它取决于代码页设置),cp1250使用例如。在Windows中(有时称为 ansi ,取决于区域设置)和iso-8859-2,有时在http服务器上使用。测试文本Tüskéshátú kígyóbűvölő归因于KoltaiLászló(原生个人姓名表单),来自wikipedia

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

我还想强调引用answerJarret Hardie JSON spec,引用:

  

字符串是零个或多个Unicode字符的集合

在我的用例中,我有json的文件。它们是utf-8个编码文件。 ensure_ascii导致正确转义但不易读取的json文件,这就是为什么我改编了Mark Amery的答案以满足我的需求。

doctest不是特别贴心,但我分享了代码,希望它对某人有用。