Python 中的 Unicode - 解析 JSON

Unicode in Python - parsing JSON

我写了这个小代码来获取 JSON 文件并将它们的内容导入 consul 键值存储 - 我很高兴递归完全按照我的预期工作,但是当源 .json 文件包含非 ASCII:

#!/usr/bin/python

import sys
import json

filename = str(sys.argv[1])
fh = open(filename)

def printDict (d, path):
  for key in d:
    if isinstance(d[key], dict):
      printDict(d[key], path + str(key) + "/")
    else:
      print 'curl -X PUT http://localhost:8500/v1/kv/' + filename + path + key + ' -d "' + str(d[key]) + '"'
  return

j = json.load(fh)
printDict(j, "/")

磁盘上的 JSON 文件失败示例:

{
    "FacetConfig" : {
        "facet:price-lf-p" : {
             "prefixParts" : "£"
        }
    }
}

当我按原样 运行 代码时,我遇到了一个讨厌的异常,因为那个简单的 str() 无法将英国货币英镑符号转换为 7 位 ASCII:

UnicodeEncodeError: 'ascii' codec can't encode character u'\xa3' in position 0: ordinal not in range(128)

我怎样才能解决这个问题,而又不至于把一开始的小而优雅的代码搞得一团糟? :)

只需从 str(d[key]) 中删除 str。也就是说,

print ('curl -X PUT http://localhost:8500/v1/kv/' + filename + 
       path + key + ' -d "' + str(d[key]) + '"')

变为:

print ('curl -X PUT http://localhost:8500/v1/kv/' + filename + 
       path + key + ' -d "' + d[key] + '"')

这里的问题是Python2中的str类型基本上仅限于ASCII字符。 type(d[key])unicode,所以你不能把它转换成 str...不过没关系,我们还是可以打印它。

而不是使用 str() 显式编码 unicode 值。由于您将值用作 URL 元素,因此您必须编码为密钥 UTF-8,然后 URL-quote ;该值只需要编码为 UTF-8。

import urllib

print ('curl -X PUT http://localhost:8500/v1/kv/' + filename + path +
       urllib.quote(key.encode('utf8')) + ' -d "' + 
       unicode(d[key]).encode('utf8') + '"')

您可以在此处使用字符串格式来提高可读性:

print 'curl -X PUT http://localhost:8500/v1/kv/{}{}{} -d "{}"'.format(
    filename, path, urllib.quote(key.encode('utf8')), 
    unicode(d[key]).encode('utf8'))

如果 d[key] 始终是字符串值,则 unicode() 调用是多余的,但如果您还有数字、布尔值或 None 值,这将确保代码继续工作.

服务器可能需要 Content-Type header;如果您确实发送了一个,或许可以考虑向 header 添加一个 charset=utf8 参数。然而,Consul 似乎将数据视为不透明。

How can I solve this without making too much of a dog's dinner of code that started out small and elegant?

遗憾的是,还需要几个额外的步骤来防止 decoding/encoding 错误。 python 2.x 有很多地方 隐式 encoding/decoding,即在你背后和未经你许可的情况下。当 python 执行隐式 encoding/decoding 时,它使用 ascii 编解码器,如果存在 utf-8(或任何其他非 ascii)字符,这将导致 encoding/decoding 错误。因此,你必须找到所有 python 隐式 encodings/decodings 的地方,并用显式 encodings/decodings 替换它们——如果你想让你的程序在那些地方处理非 ascii 字符.

至少,任何来自外部源的输入都应该在继续之前解码为 un​​icode 字符串,这意味着您必须知道输入的编码。但是,如果将 unicode 字符串与常规字符串结合使用,则会出现 encoding/decoding 错误,例如:

#-*- coding: utf-8 -*-   #Allows utf-8 characters in your source code
unicode_str = '€'.decode('utf-8')
my_str = '{0}{1}'.format('This is the Euro sign: ', unicode_str) 

--output:--
Traceback (most recent call last):
  File "1.py", line 3, in <module>
    my_str = '{0}{1}'.format('hello', unicode_str) 
UnicodeEncodeError: 'ascii' codec can't encode character u'\u20ac' in position 0: ordinal not in range(128)

因此,您的所有字符串可能都应该解码为 un​​icode 字符串。然后当你想输出字符串时,你需要对unicode字符串进行编码。

import sys
import json
import codecs
import urllib

def printDict(d, path, filename):
    for key, val in d.items():  #key is a unicode string, val is a unicode string or dict
        if isinstance(val, dict): 
            printDict(
                val,
                u'{0}{1}/'.format(path, key),  #format() specifiers require 0,1 for python 2.6
                filename
            )
        else:
            key_str = key.encode('utf-8')
            val_str = val.encode('utf-8')

            url = '{0}{1}{2} -d "{3}"'.format(
                filename, 
                path, 
                key_str, 
                val_str
            )
            print url
            url_escaped = urllib.quote(url)
            print url_escaped

            curl_cmd = 'curl -X PUT'            
            base_url = 'http://localhost:8500/v1/kv/'
            print "{0} {1}{2}".format(curl_cmd, base_url, url_escaped)


filename = sys.argv[1].decode('utf-8')
file_encoding = 'utf-8'
fh = codecs.open(filename, encoding=file_encoding)
my_json = json.load(fh)
fh.close()

print my_json

path = "/"
printDict(my_json, path.decode('utf-8'), filename)  #Can the path have  non-ascii characters in it?

--output:--
{u'FacetConfig': {u'facet:price-lf-p': {u'prefixParts': u'\xa3'}}}
data.txt/FacetConfig/facet:price-lf-p/prefixParts -d "£"
data.txt/FacetConfig/facet%3Aprice-lf-p/prefixParts%20-d%20%22%C2%A3%22
curl -X PUT http://localhost:8500/v1/kv/data.txt/FacetConfig/facet%3Aprice-lf-p/prefixParts%20-d%20%22%C2%A3%22