Модуль json Python перетворює клавіші int словника в рядки


130

Я виявив, що коли виконується наступне, модуль 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

Відповіді:


86

Це одна з тих тонких відмінностей між різними колекціями карти, які можуть вас вкусити. JSON розглядає клавіші як рядки; Python підтримує різні клавіші, що відрізняються лише типом.

У Python (і, мабуть, у Луї) ключі до відображення (словник або таблиця відповідно) - це посилання на об'єкти. У 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 (float), або 1L (long integer) ).
Джим Денніс

5
Будьте обережні з рекомендацією щодо використання Pickle. Pickle може призвести до довільного виконання коду, тому якщо джерело даних, яке ви десаріалізуєте, не є надійним, ви повинні дотримуватися "безпечного" протоколу серіалізації, як JSON. Також майте на увазі, що в міру розширення сфери проектів іноді функції, які, як ви очікували, отримаєте лише надійний вхід, починають отримувати дані, що надаються користувачем, а міркування безпеки не завжди переглядаються.
AusIV

56

Ні, у 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 завжди явно записує клавіші як рядки, навіть там, де це не здається необхідним.


1
Чому не 999999999999999999999конвертується '999999999999999999999'?
Пьотр Доброгост

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

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.


Гарне рішення для 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

Ця функція також буде працювати для вкладених диктів і використовує розуміння дикту.

Якщо ви також хочете передати значення, використовуйте:

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

Який тестує екземпляр значень і перекидає їх, лише якщо вони є об'єктами рядків (унікод, щоб бути точним).

Обидві функції передбачають, що ключі (і значення) є цілими числами.

Завдяки:

Як використовувати if / else для розуміння словника?

Перетворіть рядовий ключ у int у словнику


Це було чудово. У моєму випадку маринування не можна використовувати, тому я зберігаю кишки об’єкта за допомогою JSON шляхом перетворення в byte_array, щоб я міг використовувати стиснення. У мене є змішані ключі, тому я просто змінив ваш приклад, щоб ігнорувати ValueError, коли ключ не конвертується у int
minillinim

11

Мене вкусила та сама проблема. Як зазначали інші, у JSON ключами відображення повинні бути рядки. Можна зробити одну з двох речей. Ви можете використовувати менш сувору бібліотеку JSON, як demjson , яка дозволяє цілі рядки. Якщо жодна інша програма (або жодна інша мова) не збирається її читати, то вам слід добре. Або ви можете використовувати іншу мову серіалізації. Я б не пропонував соління. Це важко читати і не розрахований на безпеку . Натомість я б запропонував YAML, який є (майже) суперкомплектом JSON, і він дозволяє цілі клавіші. (Принаймні PyYAML робить.)


2

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

import ast
ast.literal_eval(string)

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.


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

Якщо припустити, що всі ключі в початковому диктаті є цілими числами, якщо їх можна передати до 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"}'
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.