โมดูล json ของ Python แปลงคีย์พจนานุกรม 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 ต้องเป็นประเภทที่ไม่เปลี่ยนรูปหรือต้องเป็นวัตถุที่ใช้ a__hash__วิธีการ (เอกสาร Lua แนะนำให้ใช้ ID ของอ็อบเจ็กต์เป็นแฮช / คีย์โดยอัตโนมัติแม้กระทั่งสำหรับอ็อบเจ็กต์ที่เปลี่ยนแปลงได้และอาศัยสตริงภายในเพื่อให้แน่ใจว่าสตริงที่เทียบเท่าจะแมปกับอ็อบเจ็กต์เดียวกัน)

ใน Perl, Javascript, awk และภาษาอื่น ๆ อีกมากมายคีย์สำหรับแฮชอาร์เรย์ที่เชื่อมโยงหรืออะไรก็ตามที่เรียกสำหรับภาษานั้น ๆ คือสตริง (หรือ "สเกลาร์" ใน Perl) ใน perl $foo{1}, $foo{1.0}, and $foo{"1"}ล้วนเป็นการอ้างอิงถึงการแม็ปเดียวกันใน%foo--- คีย์ถูกประเมินเป็นสเกลาร์!

JSON เริ่มต้นด้วยเทคโนโลยีอนุกรม Javascript (JSON ย่อมาจากJ ava S cript O bject N ) โดยธรรมชาติแล้วจะใช้ความหมายสำหรับสัญกรณ์การแมปซึ่งสอดคล้องกับความหมายของการทำแผนที่

หากปลายทั้งสองด้านของการทำให้เป็นอนุกรมของคุณเป็น Python คุณควรใช้ผักดองดีกว่า หากคุณต้องการแปลงกลับจาก JSON เป็นวัตถุ Python ดั้งเดิมฉันเดาว่าคุณมีทางเลือกสองทาง ขั้นแรกคุณสามารถลอง ( try: ... except: ...) เพื่อแปลงคีย์ใด ๆ เป็นตัวเลขในกรณีที่การค้นหาพจนานุกรมล้มเหลว หรือหากคุณเพิ่มโค้ดที่ปลายอีกด้านหนึ่ง (ตัวต่ออนุกรมหรือตัวสร้างของข้อมูล JSON นี้) คุณสามารถให้มันดำเนินการอนุกรม JSON กับค่าคีย์แต่ละค่าโดยระบุเป็นรายการคีย์ (จากนั้นโค้ด Python ของคุณจะวนซ้ำในรายการคีย์ก่อนโดยสร้างอินสแตนซ์ / deserializing ให้เป็นอ็อบเจกต์ Python ดั้งเดิม ... จากนั้นใช้ค่าเหล่านี้เพื่อเข้าถึงค่าจากการแมป)


1
ขอบคุณสำหรับสิ่งนั้น น่าเสียดายที่ฉันใช้ Pickle ไม่ได้ แต่ความคิดของคุณกับรายการนั้นยอดเยี่ยมมาก จะนำไปใช้ตอนนี้เชียร์สำหรับความคิด
Charles Ritchie

1
(อนึ่งใน Python 1, 1L (จำนวนเต็มยาว) และ 1.0 จะแมปไปยังคีย์เดียวกัน แต่ "1" (สตริง) ไม่ได้แมปกับ 1 (จำนวนเต็ม) หรือ 1.0 (ลอย) หรือ 1L (จำนวนเต็มแบบยาว )
Jim Dennis

5
ระมัดระวังคำแนะนำในการใช้ Pickle Pickle อาจส่งผลให้มีการเรียกใช้รหัสโดยอำเภอใจดังนั้นหากแหล่งที่มาของข้อมูลที่คุณกำลังแยกวิเคราะห์ไม่น่าเชื่อถือโดยเนื้อแท้คุณควรยึดติดกับโปรโตคอลการทำให้เป็นอนุกรมที่ "ปลอดภัย" เช่น JSON โปรดทราบว่าเมื่อขอบเขตของโปรเจ็กต์ขยายออกไปบางครั้งฟังก์ชันที่คุณคาดไว้จะได้รับเฉพาะอินพุตที่เชื่อถือได้เท่านั้นที่เริ่มได้รับอินพุตที่ผู้ใช้ให้มาและการพิจารณาด้านความปลอดภัยจะไม่ได้รับการทบทวนเสมอไป
AusIV

55

ไม่ไม่มีสิ่งที่เรียกว่า Number key ใน 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 Objects ไม่ใช่การแมปที่เหมาะสมอย่างที่คุณเข้าใจในภาษาเช่น Python และการใช้คีย์ที่ไม่ใช่ String จะทำให้เกิดความแปลก นี่คือเหตุผลที่ JSON เขียนคีย์เป็นสตริงอย่างชัดเจนเสมอแม้ว่าจะดูไม่จำเป็นก็ตาม


1
ทำไมไม่999999999999999999999แปลงเป็น'999999999999999999999'?
Piotr Dobrogost

4
@PiotrDobrogost JavaScript (เช่นเดียวกับหลายภาษา) ไม่สามารถจัดเก็บตัวเลขจำนวนมากได้โดยพลการ Numberประเภทเป็นคู่ IEEE 754ค่า floating point: คุณจะได้รับ 53 บิตของ mantissa เพื่อให้คุณสามารถเก็บได้ถึง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!
Tom Yu

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)แล้วแปลงกลับเป็น 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

ฉันสร้างส่วนขยายที่ง่ายมากของคำตอบของ Murmel ซึ่งฉันคิดว่าจะใช้งานได้กับพจนานุกรมโดยพลการ (รวมทั้งแบบซ้อน) โดยสมมติว่า 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"}'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.