Модуль Python json, преобразует ключи словаря int в строки

132

Я обнаружил, что при следующем запуске json-модуль python (включенный с версии 2.6) преобразует ключи словаря int в строки.

>>> import json
>>> releases = {1: "foo-v0.1"}
>>> json.dumps(releases)
'{"1": "foo-v0.1"}'

Есть ли простой способ сохранить ключ как int, без необходимости анализировать строку при дампе и загрузке. Я считаю, что можно было бы использовать хуки, предоставленные модулем json, но опять же, это все еще требует синтаксического анализа. Есть ли аргумент, который я упустил из виду? ура, Чаз

Подвопрос: Спасибо за ответы. Видя, как json работает так, как я опасался, есть ли простой способ передать тип ключа, возможно, проанализировав вывод дампа? Также я должен отметить, что код, выполняющий дамп, и код, загружающий объект json с сервера и загружающий его, написаны мной.

Чарльз Ричи
источник
23
ключи json должны быть строками
tonfa

Ответы:

87

Это одно из тех тонких различий между различными коллекциями карт, которые могут вас укусить. JSON обрабатывает ключи как строки; Python поддерживает отдельные ключи, различающиеся только типом.

В Python (и, очевидно, в Lua) ключи к отображению (словарь или таблица, соответственно) являются ссылками на объекты. В Python они должны быть неизменяемыми типами или должны быть объектами, реализующими __hash__метод. (Документы Lua предполагают, что он автоматически использует идентификатор объекта в качестве хэша / ключа даже для изменяемых объектов и полагается на интернирование строк, чтобы гарантировать, что эквивалентные строки сопоставляются с одними и теми же объектами).

В Perl, Javascript, awk и многих других языках ключи для хэшей, ассоциативных массивов или того, что они называются для данного языка, являются строками (или «скалярами» в Perl). В Perl $foo{1}, $foo{1.0}, and $foo{"1"}все ссылки на одно и то же отображение %foo--- ключ оценивается как скаляр!

JSON начинался как технология сериализации Javascript. (JSON означает J AVA S cript O ▪ Таблица N otation.) Естественно , что она реализует семантику для его отображения обозначений , которые согласуются с его отображения семантики.

Если оба конца вашей сериализации будут Python, вам лучше использовать соленья. Если вам действительно нужно преобразовать их обратно из JSON в собственные объекты Python, я думаю, у вас есть несколько вариантов. Сначала вы можете попробовать ( try: ... except: ...) преобразовать любой ключ в число в случае сбоя поиска по словарю. В качестве альтернативы, если вы добавите код на другой конец (сериализатор или генератор этих данных JSON), вы можете заставить его выполнить сериализацию JSON для каждого из значений ключа, предоставив их в виде списка ключей. (Тогда ваш код Python сначала будет перебирать список ключей, создавая / десериализуя их в собственные объекты Python ... а затем использовать их для доступа к значениям из сопоставления).

Джим Деннис
источник
1
Спасибо за это. К сожалению, я не могу использовать Pickle, но ваша идея со списком прекрасна. Реализуем это сейчас, приветствую идею.
Чарльз Ричи,
1
(Кстати, в Python 1 1L (длинное целое число) и 1.0 сопоставляются с одним и тем же ключом; но «1» (строка) не соответствует тому же, что и 1 (целое число), 1.0 (с плавающей запятой) или 1L (длинное целое число) )
Джим Деннис,
5
Будьте осторожны с рекомендацией использовать рассол. Pickle может привести к выполнению произвольного кода, поэтому, если источник данных, которые вы десериализуете, не заслуживает доверия, вам следует придерживаться «безопасного» протокола сериализации, такого как JSON. Также имейте в виду, что по мере расширения масштабов проектов иногда функции, которые, как вы ожидали, будут получать только доверенный ввод, начинают получать вводимые пользователем данные, а соображения безопасности не всегда пересматриваются.
AusIV
55

Нет, в JavaScript нет такой вещи, как цифровая клавиша. Все свойства объекта преобразуются в String.

var a= {1: 'a'};
for (k in a)
    alert(typeof k); // 'string'

Это может привести к появлению странного поведения:

a[999999999999999999999]= 'a'; // this even works on Array
alert(a[1000000000000000000000]); // 'a'
alert(a['999999999999999999999']); // fail
alert(a['1e+21']); // 'a'

Объекты JavaScript на самом деле не совсем правильные сопоставления, как вы понимаете на таких языках, как Python, а использование ключей, которые не являются String, приводит к странностям. Вот почему JSON всегда явно записывает ключи в виде строк, даже если это не кажется необходимым.

bobince
источник
1
Почему не 999999999999999999999конвертируется в '999999999999999999999'?
Петр Доброгост
4
@PiotrDobrogost JavaScript (как и многие другие языки) не может хранить произвольно большие числа. NumberТип представляет собой двойной IEEE 754 значение с плавающей запятой: вы получаете 53 бит мантиссы, так что вы можете хранить до 2⁵³ (9007199254740992) с целочисленной точностью; помимо этого целые числа будут округляться до других значений (следовательно, 9007199254740993 === 9007199254740992). 999999999999999999999 округляется до 1000000000000000000000, для которого используется toStringпредставление по умолчанию 1e+21.
bob с 08
22

В качестве альтернативы вы также можете попробовать преобразовать словарь в список формата [(k1, v1), (k2, v2)] при его кодировании с помощью json и преобразовать обратно в словарь после его обратного декодирования.


>>>> import json
>>>> json.dumps(releases.items())
    '[[1, "foo-v0.1"]]'
>>>> releases = {1: "foo-v0.1"}
>>>> releases == dict(json.loads(json.dumps(releases.items())))
     True
Я считаю, что для этого потребуется дополнительная работа, например, наличие какого-то флага, чтобы определить, какие все параметры будут преобразованы в словарь после его декодирования из json.

Ashish
источник
Хорошее решение для объектов dict без вложенных объектов dict!
Том Ю
15

Отвечая на ваш вопрос:

Это можно сделать, используя json.loads(jsonDict, object_hook=jsonKeys2int)

def jsonKeys2int(x):
    if isinstance(x, dict):
            return {int(k):v for k,v in x.items()}
    return x

Эта функция также будет работать для вложенных dicts и использует понимание dict.

Если вы хотите также использовать значения, используйте:

def jsonKV2int(x):
    if isinstance(x, dict):
            return {int(k):(int(v) if isinstance(v, unicode) else v) for k,v in x.items()}
    return x

Что проверяет экземпляр значений и отбрасывает их только в том случае, если они являются строковыми объектами (точнее, Unicode).

Обе функции предполагают, что ключи (и значения) являются целыми числами.

Благодаря:

Как использовать if / else в понимании словаря?

Преобразование строкового ключа в int в словаре

Murmel
источник
Это было здорово. В моем случае травление использовать нельзя, поэтому я сохраняю внутренности объекта, используя JSON, путем преобразования в byte_array, чтобы я мог использовать сжатие. У меня смешанные ключи, поэтому я просто изменил ваш пример, чтобы игнорировать ValueError, когда ключ не может быть преобразован в int
minillinim
11

Меня укусила та же проблема. Как указывали другие, в JSON ключи сопоставления должны быть строками. Вы можете сделать одно из двух. Вы можете использовать менее строгую библиотеку JSON, например demjson , которая позволяет использовать целочисленные строки. Если никакие другие программы (или никакие другие программы на других языках) не собираются его читать, значит, все в порядке. Или вы можете использовать другой язык сериализации. Я бы не советовал рассол. Его трудно читать, и он не предназначен для обеспечения безопасности . Вместо этого я бы предложил YAML, который (почти) является расширенным набором JSON и допускает целочисленные ключи. (По крайней мере, PyYAML .)

AFoglia
источник
2

Преобразуйте словарь в строку с помощью, str(dict)а затем преобразуйте его обратно в dict, выполнив следующие действия:

import ast
ast.literal_eval(string)
Hzzkygcs
источник
1

Вот мое решение! Я использовал object_hook, это полезно, когда вы вложилиjson

>>> import json
>>> json_data = '{"1": "one", "2": {"-3": "minus three", "4": "four"}}'
>>> py_dict = json.loads(json_data, object_hook=lambda d: {int(k) if k.lstrip('-').isdigit() else k: v for k, v in d.items()})

>>> py_dict
{1: 'one', 2: {-3: 'minus three', 4: 'four'}}

Есть фильтр только для разбора json-ключа на int. Вы также можете использовать int(v) if v.lstrip('-').isdigit() else vфильтр для значения json.

GooDeeJaY
источник
1

Я сделал очень простое расширение ответа Мурмеля, которое, как я думаю, будет работать с довольно произвольным словарем (включая вложенный), предполагая, что он может быть сброшен JSON в первую очередь. Любые ключи, которые можно интерпретировать как целые числа, будут преобразованы в int. Без сомнения, это не очень эффективно, но это работает для моих целей хранения и загрузки из строк json.

def convert_keys_to_int(d: dict):
    new_dict = {}
    for k, v in d.items():
        try:
            new_key = int(k)
        except ValueError:
            new_key = k
        if type(v) == dict:
            v = _convert_keys_to_int(v)
        new_dict[new_key] = v
    return new_dict

Предполагая, что все ключи в исходном dict являются целыми числами, если они могут быть преобразованы в int, тогда это вернет исходный словарь после сохранения как json. например

>>>d = {1: 3, 2: 'a', 3: {1: 'a', 2: 10}, 4: {'a': 2, 'b': 10}}
>>>convert_keys_to_int(json.loads(json.dumps(d)))  == d
True
Тим Чайлд
источник
-1

Вы можете написать свое json.dumpsсамостоятельно, вот пример из djson : encoder.py . Вы можете использовать это так:

assert dumps({1: "abc"}) == '{1: "abc"}'
damnever
источник