2015-01-22 2 views
0

Я написал этот маленький код, чтобы взять файлы JSON и импортировать их содержимое в хранилище ключей для consul - я был очень доволен, что рекурсия работает точно так, как я ожидаю, однако менее радует, когда исходные .json файлы содержали не-ASCII:Unicode в Python - разбор JSON

#!/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" : "£" 
     } 
    } 
} 

Когда я запускаю код как есть, я получаю неприятное исключение, потому что приятно simple str() не может преобразовать знак британского фунта стерлингов в 7-битный ASCII:

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

Как я могу решить это, не делая слишком много ужина с собакой кода, который начинался с малого и элегантного? :)

ответ

1

Вместо использования str(), encode значение 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')) 

unicode() вызов является излишним, если d[key] всегда строковое значение, но если у вас также есть номера, булевы или None, это позволит убедиться, что код продолжает работать.

Сервер может ожидать заголовок Content-Type; если вы его отправляете, возможно, подумайте о добавлении в заголовок параметра charset=utf8. Похоже, Консул рассматривает данные как непрозрачные.

+0

Хм, это d [ключ], который содержит знак волшебства, однако я получаю более драматичную ошибку: AttributeError: объект 'module' не имеет атрибута 'urlquote' – gdhgdh

+0

@gdhgdh: right, typo с моей стороны. Вы по-прежнему хотите правильно закодировать свой URL. –

+0

Я потратил немного времени, чтобы на самом деле прочитать ошибку и заметил, что это еще одна часть кода, поэтому я удалил следующий вопрос. Спасибо, что ответили все равно! :) – gdhgdh

1

Просто удалите str от str(d[key]). То есть,

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] + '"') 

Проблема здесь состоит в том, что str типа в Python 2, в основном ограничивается ASCII символов. type(d[key]) - unicode, поэтому вы не можете преобразовать его в str ... но это нормально, мы можем его распечатать.

+0

Достаточно честный, я дал неполный источник; реальные файлы - сотни строк и содержат bools и integers, которые str() также обеспечивает отличное представление. Следовательно, просто удаление str() не помогает (я уже пробовал это;) – gdhgdh

+0

@gdhgdh: тогда используйте 'unicode()', а не 'str()' и кодируем. –

+0

Простое использование unicode() решило его - не понимало, что это даже вещь. Удивительно, спасибо. Теперь, как мне ответить на ответ, который на самом деле не был ответом? :) – gdhgdh

0

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

К сожалению, существует несколько дополнительных шагов, необходимых для предотвращения ошибок декодирования/кодирования. python 2.x имеет множество мест, где неявный кодирования/декодирования, т. е. за вашей спиной и без вашего разрешения. Когда python выполняет неявное кодирование/декодирование, он использует ascii-кодек, что приведет к ошибке кодирования/декодирования, если присутствует символ utf-8 (или любой другой не-ascii). В результате вы должны найти все места, где python выполняет неявные кодировки/декодирования и заменять их явными кодировками/декодированием - если вы хотите, чтобы ваша программа обрабатывала символы не-ascii в этих местах.

По крайней мере, любой вход внешнего источника должен быть декодирован в строку юникода перед продолжением, что означает, что вам нужно знать кодировку ввода.Но если объединить Юникод строку с регулярными строками, вы можете получить ошибки кодирования/декодирования, например:

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

Поэтому все ваши строки, вероятно, должны быть расшифрованы в юникоде строки. Затем, когда вы хотите вывести строки, вам нужно закодировать строки юникода.

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 
+0

Не используйте 'codecs.open()'; 'json.load()' ожидает загрузки байтов в Python 2. –

+0

* Кодировки, которые не основаны на ASCII (например, UCS-2), недопустимы и должны быть обернуты codecs.getreader (encoding) (fp), или просто декодируется в объект unicode и передается в load(). * https://docs.python.org/2/library/json.html#module-json – 7stud

+0

Извините, неправильно прочитайте это. Лично я бы избегал 'codecs.getreader()', так как существует множество проблем с реализацией ввода-вывода 'codecs'. Вместо этого используйте 'io.open()'. Но ясно, что проблема с OP не связана с входным файлом. –

Смежные вопросы