Mô-đun json của Python, chuyển đổi các khóa từ điển int thành chuỗi


130

Tôi đã thấy rằng khi chạy dưới đây, mô-đun json của python (bao gồm từ 2.6) chuyển đổi các khóa từ điển int thành chuỗi.

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

Có cách nào dễ dàng để bảo quản khóa dưới dạng int mà không cần phân tích chuỗi trên kết xuất và tải. Tôi tin rằng có thể sử dụng các hook được cung cấp bởi mô-đun json, nhưng một lần nữa điều này vẫn đòi hỏi phải phân tích cú pháp. Có thể có một cuộc tranh luận mà tôi đã bỏ qua? chúc mừng, chaz

Câu hỏi phụ: Cảm ơn câu trả lời. Xem như json hoạt động như tôi sợ, có cách nào dễ dàng để truyền đạt loại khóa bằng cách có thể phân tích cú pháp đầu ra của các bãi? Ngoài ra tôi nên lưu ý mã thực hiện việc bán phá giá và mã tải xuống đối tượng json từ máy chủ và tải nó, đều do tôi viết.


23
Các khóa json phải là các chuỗi
tonfa

Câu trả lời:


86

Đây là một trong những khác biệt tinh tế giữa các bộ sưu tập bản đồ khác nhau có thể cắn bạn. JSON coi các khóa là chuỗi; Python hỗ trợ các khóa riêng biệt chỉ khác nhau về loại.

Trong Python (và dường như trong Lua), các khóa để ánh xạ (từ điển hoặc bảng tương ứng) là các tham chiếu đối tượng. Trong Python, chúng phải là các kiểu bất biến hoặc chúng phải là các đối tượng thực hiện một__hash__ phương thức. (Các tài liệu Lua đề xuất rằng nó tự động sử dụng ID của đối tượng làm hàm băm / khóa ngay cả đối với các đối tượng có thể thay đổi và dựa vào thực tập chuỗi để đảm bảo rằng các chuỗi tương đương ánh xạ tới cùng các đối tượng).

Trong Perl, Javascript, awk và nhiều ngôn ngữ khác, các khóa để băm, mảng kết hợp hoặc bất cứ thứ gì chúng được gọi cho ngôn ngữ đã cho, là các chuỗi (hoặc "vô hướng" trong Perl). Trong perl $foo{1}, $foo{1.0}, and $foo{"1"}đều tham chiếu đến các bản đồ tương tự trong %foo--- phím được đánh giá là vô hướng!

JSON bắt đầu như một công nghệ tuần tự hóa Javascript. (JSON là viết tắt của J ava S cript O bject N otation.) Đương nhiên, nó thực hiện ngữ nghĩa cho ký hiệu ánh xạ phù hợp với ngữ nghĩa ánh xạ của nó.

Nếu cả hai đầu của tuần tự hóa của bạn sẽ là Python thì tốt hơn bạn nên sử dụng dưa chua. Nếu bạn thực sự cần chuyển đổi những thứ này trở lại từ JSON thành các đối tượng Python nguyên gốc, tôi đoán bạn có một vài lựa chọn. Trước tiên, bạn có thể thử ( try: ... except: ...) để chuyển đổi bất kỳ khóa nào thành số trong trường hợp lỗi tra cứu từ điển. Ngoài ra, nếu bạn thêm mã vào đầu bên kia (trình tuần tự hóa hoặc trình tạo dữ liệu JSON này) thì bạn có thể yêu cầu nó thực hiện tuần tự hóa JSON trên mỗi giá trị khóa --- cung cấp chúng dưới dạng danh sách các khóa. (Sau đó, mã Python của bạn trước tiên sẽ lặp lại danh sách các khóa, khởi tạo / giải tuần tự chúng thành các đối tượng Python nguyên gốc ... và sau đó sử dụng chúng để truy cập các giá trị ngoài ánh xạ).


1
Cảm ơn vì điều đó. Thật không may, tôi không thể sử dụng Pickle, nhưng ý tưởng của bạn với danh sách này là tuyệt vời. Sẽ thực hiện điều đó bây giờ, cổ vũ cho ý tưởng.
Charles Ritchie

1
(Ngẫu nhiên, trong Python 1, 1L (số nguyên dài) và ánh xạ 1.0 vào cùng một khóa; nhưng "1" (một chuỗi) không ánh xạ giống như 1 (số nguyên) hoặc 1.0 (float) hoặc 1L (số nguyên dài ).
Jim Dennis

5
Hãy thận trọng với khuyến nghị sử dụng Pickle. Pickle có thể dẫn đến việc thực thi mã tùy ý, vì vậy nếu nguồn dữ liệu mà bạn khử lưu huỳnh không đáng tin cậy, bạn nên sử dụng giao thức tuần tự hóa "an toàn" như JSON. Ngoài ra, hãy nhớ rằng khi phạm vi của các dự án mở rộng, đôi khi các chức năng mà bạn mong đợi sẽ chỉ nhận được đầu vào đáng tin cậy bắt đầu được cung cấp bởi người dùng và các cân nhắc về bảo mật không phải luôn được xem xét lại.
AusIV

56

Không, không có thứ gọi là khóa Số trong JavaScript. Tất cả các thuộc tính đối tượng được chuyển đổi thành Chuỗi.

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

Điều này có thể dẫn đến một số hành vi có vẻ tò mò:

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

Các đối tượng JavaScript không thực sự ánh xạ đúng như bạn hiểu nó bằng các ngôn ngữ như Python và sử dụng các khóa không phải là Chuỗi dẫn đến sự kỳ lạ. Đây là lý do tại sao JSON luôn ghi rõ ràng các khóa dưới dạng chuỗi, ngay cả khi không cần thiết.


1
Tại sao không được 999999999999999999999chuyển đổi thành '999999999999999999999'?
Piotr Dobrogost

4
@PiotrDobrogost JavaScript (giống như nhiều ngôn ngữ) không thể lưu trữ số lượng lớn tùy ý. Các Numberloại là một đôi IEEE 754 giá trị dấu chấm động: bạn nhận được 53 bit của mantissa, vì vậy bạn có thể lưu trữ lên đến 2⁵³ (9007199254740992) với độ chính xác số nguyên; ngoài số nguyên đó sẽ làm tròn đến các giá trị khác (do đó 9007199254740993 === 9007199254740992). 999999999999999999999 làm tròn thành 1000000000000000000000, với toStringđại diện mặc định là 1e+21.
bobince

22

Ngoài ra, bạn cũng có thể thử chuyển đổi từ điển sang danh sách định dạng [(k1, v1), (k2, v2)] trong khi mã hóa bằng json và chuyển đổi lại từ điển sau khi giải mã lại.


>>>> import json
>>>> json.dumps(releases.items())
    '[[1, "foo-v0.1"]]'
>>>> releases = {1: "foo-v0.1"}
>>>> releases == dict(json.loads(json.dumps(releases.items())))
     True
Tôi tin rằng điều này sẽ cần một số công việc nữa như có một số loại cờ để xác định tất cả các tham số sẽ được chuyển đổi thành từ điển sau khi giải mã nó từ json.


Giải pháp tốt cho các đối tượng dict mà không có đối tượng dict lồng nhau!
Tom Yu

15

Trả lời câu hỏi phụ của bạn:

Nó có thể được thực hiện bằng cách sử dụng 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

Chức năng này cũng sẽ làm việc cho các dicts lồng nhau và sử dụng một sự hiểu biết chính tả.

Nếu bạn cũng muốn truyền các giá trị, hãy sử dụng:

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

Việc kiểm tra thể hiện của các giá trị và chỉ sử dụng chúng nếu chúng là các đối tượng chuỗi (chính xác là unicode).

Cả hai hàm đều giả sử các khóa (và giá trị) là số nguyên.

Nhờ vào:

Làm thế nào để sử dụng if / other trong một từ điển hiểu?

Chuyển đổi khóa chuỗi thành int trong Từ điển


Điều này thật tuyệt Trong trường hợp của tôi, không thể sử dụng phương pháp tẩy, vì vậy tôi sẽ tiết kiệm can đảm của một đối tượng bằng cách sử dụng JSON thông qua chuyển đổi thành byte_array để tôi có thể sử dụng tính năng nén. Tôi đã nhận được các khóa hỗn hợp, vì vậy tôi chỉ sửa đổi ví dụ của bạn để bỏ qua ValueError khi khóa không thể chuyển đổi thành int
minillinim

11

Tôi đã bị cắn bởi cùng một vấn đề. Như những người khác đã chỉ ra, trong JSON, các khóa ánh xạ phải là các chuỗi. Bạn có thể làm một trong hai việc. Bạn có thể sử dụng một thư viện JSON ít nghiêm ngặt hơn, như demjson , cho phép các chuỗi số nguyên. Nếu không có chương trình nào khác (hoặc không có ngôn ngữ nào khác) sẽ đọc nó, thì bạn sẽ ổn thôi. Hoặc bạn có thể sử dụng một ngôn ngữ tuần tự hóa khác. Tôi sẽ không đề nghị dưa chua. Nó khó đọc và không được thiết kế để bảo mật . Thay vào đó, tôi đề nghị YAML, gần như là một siêu bộ JSON và không cho phép các khóa số nguyên. (Ít nhất là PyYAML .)


2

Chuyển đổi từ điển thành chuỗi bằng cách sử dụng str(dict)và sau đó chuyển đổi lại thành dict bằng cách thực hiện điều này:

import ast
ast.literal_eval(string)

1

Đây là giải pháp của tôi! Tôi đã sử dụng object_hook, nó rất hữu ích khi bạn đã lồngjson

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

Có bộ lọc chỉ để phân tích khóa json thành int. Bạn có thể sử dụng int(v) if v.lstrip('-').isdigit() else vbộ lọc cho giá trị json quá.


1

Tôi đã thực hiện một phần mở rộng rất đơn giản cho câu trả lời của Murmel mà tôi nghĩ sẽ hoạt động trên một từ điển khá độc đoán (bao gồm cả lồng nhau) với giả định rằng nó có thể bị JSON bỏ ngay từ đầu. Bất kỳ khóa nào có thể được hiểu là số nguyên sẽ được chuyển thành int. Không nghi ngờ gì điều này không hiệu quả lắm, nhưng nó hoạt động cho mục đích lưu trữ và tải từ chuỗi json của tôi.

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

Giả sử rằng tất cả các khóa trong dict gốc là số nguyên nếu chúng có thể được chuyển thành int, thì điều này sẽ trả về từ điển gốc sau khi lưu trữ dưới dạng json. ví dụ

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

Bạn có thể tự viết json.dumps, đây là một ví dụ từ djson : encoder.py . Bạn có thể sử dụng nó như thế này:

assert dumps({1: "abc"}) == '{1: "abc"}'
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.