Das json-Modul von Python konvertiert int-Wörterbuchschlüssel in Zeichenfolgen

130

Ich habe festgestellt, dass das json-Modul von Python (seit 2.6 enthalten) int-Wörterbuchschlüssel in Zeichenfolgen konvertiert, wenn Folgendes ausgeführt wird.

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

Gibt es eine einfache Möglichkeit, den Schlüssel als int beizubehalten, ohne die Zeichenfolge beim Speichern und Laden analysieren zu müssen? Ich glaube, es wäre möglich, die vom json-Modul bereitgestellten Hooks zu verwenden, aber auch dies erfordert noch eine Analyse. Gibt es möglicherweise ein Argument, das ich übersehen habe? Prost, chaz

Unterfrage: Danke für die Antworten. Gibt es eine einfache Möglichkeit, den Schlüsseltyp zu übermitteln, indem Sie möglicherweise die Ausgabe von Dumps analysieren, da json so funktioniert, wie ich befürchtet habe? Außerdem sollte ich beachten, dass der Code, der das Dumping ausführt, und der Code, der das JSON-Objekt von einem Server herunterlädt und lädt, beide von mir geschrieben wurden.

Charles Ritchie
quelle
23
JSON-Tasten müssen Zeichenfolgen sein
Tonfa

Antworten:

86

Dies ist einer dieser subtilen Unterschiede zwischen verschiedenen Mapping-Sammlungen, die Sie beißen können. JSON behandelt Schlüssel als Zeichenfolgen. Python unterstützt unterschiedliche Schlüssel, die sich nur im Typ unterscheiden.

In Python (und anscheinend in Lua) sind die Schlüssel für eine Zuordnung (Wörterbuch bzw. Tabelle) Objektreferenzen. In Python müssen sie unveränderliche Typen sein oder Objekte, die eine __hash__Methode implementieren . (Die Lua-Dokumente schlagen vor, dass die Objekt-ID automatisch als Hash / Schlüssel verwendet wird, auch für veränderbare Objekte, und dass die interne Zeichenfolge verwendet wird, um sicherzustellen, dass äquivalente Zeichenfolgen denselben Objekten zugeordnet werden.)

In Perl, Javascript, awk und vielen anderen Sprachen sind die Schlüssel für Hashes, assoziative Arrays oder wie auch immer sie für die angegebene Sprache genannt werden, Zeichenfolgen (oder "Skalare" in Perl). In Perl $foo{1}, $foo{1.0}, and $foo{"1"}sind alle Verweise auf dasselbe Mapping in %foo--- der Schlüssel wird als Skalar ausgewertet !

JSON wurde als Javascript-Serialisierungstechnologie gestartet. (JSON steht für J ava S cript O bject N otation.) Natürlich implementiert es Semantik für seine Mapping-Notation, die mit seiner Mapping-Semantik übereinstimmt.

Wenn beide Enden Ihrer Serialisierung Python sein sollen, sollten Sie Pickles verwenden. Wenn Sie diese wirklich von JSON zurück in native Python-Objekte konvertieren müssen, haben Sie vermutlich mehrere Möglichkeiten. Zuerst könnten Sie versuchen ( try: ... except: ...), einen beliebigen Schlüssel in eine Zahl umzuwandeln, falls die Suche nach einem Wörterbuch fehlschlägt. Wenn Sie dem anderen Ende (dem Serializer oder Generator dieser JSON-Daten) Code hinzufügen, können Sie alternativ eine JSON-Serialisierung für jeden der Schlüsselwerte durchführen lassen - und diese als Liste der Schlüssel bereitstellen. (Dann iteriert Ihr Python-Code zuerst über die Liste der Schlüssel, instanziiert / deserialisiert sie in native Python-Objekte ... und verwendet diese dann für den Zugriff auf die Werte aus der Zuordnung).

Jim Dennis
quelle
1
Dank dafür. Leider kann ich Pickle nicht verwenden, aber Ihre Idee mit der Liste ist großartig. Werde das jetzt umsetzen, Prost auf die Idee.
Charles Ritchie
1
(Übrigens werden in Python 1 1L (lange Ganzzahl) und 1.0 demselben Schlüssel zugeordnet, aber "1" (eine Zeichenfolge) wird nicht 1 (Ganzzahl) oder 1.0 (float) oder 1L (lange Ganzzahl) zugeordnet ).
Jim Dennis
5
Seien Sie vorsichtig mit der Empfehlung, Pickle zu verwenden. Pickle kann zu einer willkürlichen Codeausführung führen. Wenn die Quelle der Daten, die Sie deserialisieren, nicht von Natur aus vertrauenswürdig ist, sollten Sie sich an ein "sicheres" Serialisierungsprotokoll wie JSON halten. Beachten Sie auch, dass Funktionen, von denen Sie erwartet haben, dass sie nur vertrauenswürdige Eingaben erhalten, wenn der Umfang der Projekte erweitert wird, manchmal vom Benutzer bereitgestellte Eingaben erhalten und die Sicherheitsüberlegungen nicht immer überprüft werden.
AusIV
56

Nein, in JavaScript gibt es keinen Zahlenschlüssel. Alle Objekteigenschaften werden in String konvertiert.

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

Dies kann zu merkwürdig wirkenden Verhaltensweisen führen:

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

JavaScript-Objekte sind keine wirklich richtigen Zuordnungen, wie Sie sie in Sprachen wie Python verstehen würden, und die Verwendung von Schlüsseln, die keine Zeichenfolgen sind, führt zu Verrücktheit. Aus diesem Grund schreibt JSON Schlüssel immer explizit als Zeichenfolgen, auch wenn dies nicht erforderlich erscheint.

Bobince
quelle
1
Warum wird nicht 999999999999999999999konvertiert '999999999999999999999'?
Piotr Dobrogost
4
@PiotrDobrogost JavaScript (wie viele andere Sprachen) kann keine beliebig großen Zahlen speichern. Der NumberTyp ist ein IEEE 754-Doppel- Gleitkommawert: Sie erhalten 53 Bit Mantisse, sodass Sie bis zu 2⁵³ (9007199254740992) mit ganzzahliger Genauigkeit speichern können. Darüber hinaus werden ganze Zahlen auf andere Werte gerundet (daher 9007199254740993 === 9007199254740992). 999999999999999999999 rundet auf 1000000000000000000000, für die die Standarddarstellung toStringlautet 1e+21.
Bobince
22

Alternativ können Sie auch versuchen, das Wörterbuch in eine Liste der Formate [(k1, v1), (k2, v2)] zu konvertieren, während Sie es mit json codieren, und es nach dem Zurückcodieren wieder in ein Wörterbuch konvertieren.


>>>> import json
>>>> json.dumps(releases.items())
    '[[1, "foo-v0.1"]]'
>>>> releases = {1: "foo-v0.1"}
>>>> releases == dict(json.loads(json.dumps(releases.items())))
     True
Ich glaube, dies erfordert etwas mehr Arbeit, wie eine Art Flag, um zu identifizieren, welche Parameter nach dem Zurückcodieren von json in ein Wörterbuch konvertiert werden sollen.

Ashish
quelle
Gute Lösung für Diktierobjekte ohne verschachtelte Diktierobjekte!
Tom Yu
15

Beantwortung Ihrer Unterfrage:

Dies kann durch Verwendung erreicht werden 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

Diese Funktion funktioniert auch für verschachtelte Diktate und verwendet ein Diktatverständnis.

Wenn Sie die Werte auch umwandeln möchten, verwenden Sie:

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

Dadurch wird die Instanz der Werte getestet und nur dann umgewandelt, wenn es sich um Zeichenfolgenobjekte handelt (um genau zu sein Unicode).

Beide Funktionen setzen voraus, dass Schlüssel (und Werte) Ganzzahlen sind.

Dank an:

Wie verwende ich if / else in einem Wörterbuchverständnis?

Konvertieren Sie einen Zeichenfolgenschlüssel in einem Wörterbuch in int

Murmel
quelle
Das war großartig. In meinem Fall kann das Beizen nicht verwendet werden, daher speichere ich die Eingeweide eines Objekts mithilfe von JSON durch Konvertierung in ein byte_array, damit ich die Komprimierung verwenden kann. Ich habe gemischte Schlüssel, also habe ich Ihr Beispiel so geändert, dass ein ValueError ignoriert wird, wenn der Schlüssel nicht in einen int
minillinim
11

Ich bin von dem gleichen Problem gebissen worden. Wie bereits erwähnt, müssen die Zuordnungsschlüssel in JSON Zeichenfolgen sein. Sie können eines von zwei Dingen tun. Sie können eine weniger strenge JSON-Bibliothek wie demjson verwenden , die ganzzahlige Zeichenfolgen zulässt. Wenn keine anderen Programme (oder keine anderen in anderen Sprachen) es lesen werden, sollten Sie in Ordnung sein. Oder Sie können eine andere Serialisierungssprache verwenden. Ich würde keine Gurke vorschlagen. Es ist schwer zu lesen und nicht sicher . Stattdessen würde ich YAML vorschlagen, das (fast) eine Obermenge von JSON ist und ganzzahlige Schlüssel zulässt. (Zumindest PyYAML .)

AFoglia
quelle
2

Konvertieren Sie das Wörterbuch mithilfe str(dict)von: in einen String und konvertieren Sie es dann wieder in ein Diktat:

import ast
ast.literal_eval(string)
Hzzkygcs
quelle
1

Hier ist meine Lösung! Ich habe verwendet object_hook, es ist nützlich, wenn Sie verschachtelt habenjson

>>> 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'}}

Es gibt nur einen Filter zum Parsen des JSON-Schlüssels auf int. Sie können den int(v) if v.lstrip('-').isdigit() else vFilter auch für den JSON-Wert verwenden.

GooDeeJaY
quelle
1

Ich habe eine sehr einfache Erweiterung von Murmels Antwort gemacht, von der ich denke, dass sie mit einem ziemlich willkürlichen Wörterbuch (einschließlich verschachtelter Wörterbücher) funktioniert, vorausgesetzt, es kann überhaupt von JSON ausgegeben werden. Alle Schlüssel, die als Ganzzahlen interpretiert werden können, werden in int umgewandelt. Zweifellos ist dies nicht sehr effizient, aber es funktioniert für meine Zwecke zum Speichern und Laden von JSON-Strings.

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

Angenommen, alle Schlüssel im ursprünglichen Diktat sind Ganzzahlen, wenn sie in int umgewandelt werden können, wird das ursprüngliche Wörterbuch nach dem Speichern als JSON zurückgegeben. z.B

>>>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
Tim Kind
quelle
-1

Sie können Ihre json.dumpsselbst schreiben , hier ist ein Beispiel von djson : encoder.py . Sie können es so verwenden:

assert dumps({1: "abc"}) == '{1: "abc"}'
verdammt noch mal
quelle