Pythonのjsonモジュール、int辞書キーを文字列に変換します


130

以下を実行すると、Pythonのjsonモジュール(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

これは、さまざまなマッピングコレクション間での微妙な違いの1つです。JSONはキーを文字列として扱います。Pythonは、タイプのみが異なる個別のキーをサポートしています。

Python(および明らかにLua)では、マッピング(それぞれ辞書またはテーブル)のキーはオブジェクト参照です。Pythonでは、これらは不変の型であるか、または__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 otation。)当然のことながら、そのマッピングの意味と一致するそのマッピング表記のためのセマンティクスを実装します。

シリアライゼーションの両端がPythonになる場合は、ピクルスを使用したほうがよいでしょう。これらをJSONからネイティブPythonオブジェクトに変換する必要がある場合は、いくつかの選択肢があると思います。まず、(try: ... except: ...)を試して、辞書の検索に失敗した場合に任意のキーを数値に変換できます。または、もう一方の端(このJSONデータのシリアライザーまたはジェネレーター)にコードを追加すると、各キー値に対してJSONシリアル化を実行させることができます-それらをキーのリストとして提供します。(その後、Pythonコードは最初にキーのリストを反復処理し、それらをネイティブのPythonオブジェクトにインスタンス化/逆シリアル化します...次に、それらを使用してマッピングから値にアクセスします)。


1
それをありがとう。残念ながら私はピクルスを使用することはできませんが、リストでのあなたのアイデアは素晴らしいです。今それを実装します、アイデアを応援します。
チャールズリッチー

1
(ちなみに、Python 1では、1L(長整数)、および1.0は同じキーにマップされますが、 "1"(文字列)は1(整数)または1.0(浮動小数点)または1L(長整数)と同じにマップされません。 )。
ジム・デニス

5
Pickleの使用を推奨することに注意してください。Pickleは任意のコードを実行する可能性があるため、逆シリアル化するデータのソースが本質的に信頼できない場合は、JSONなどの「安全な」シリアル化プロトコルを使用する必要があります。また、プロジェクトの範囲が拡大すると、信頼できる入力しか得られないことが予想される機能がユーザー提供の入力を取得し始め、セキュリティの考慮事項が常に再検討されるとは限らないことにも注意してください。
AusIV 2016年

56

いいえ、JavaScriptには数字キーなどはありません。すべてのオブジェクトプロパティは文字列に変換されます。

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などの言語では理解できるように、実際には適切なマッピングではなく、文字列ではないキーを使用すると、奇妙な結果になります。これが、JSONが必要でないように見えても、常に明示的にキーを文字列として書き込む理由です。


1
999999999999999999999変換されないのはなぜ'999999999999999999999'ですか?
Piotr Dobrogost

4
@PiotrDobrogost JavaScript(多くの言語と同様)は、任意の大きな数値を格納できません。Numberタイプは、IEEE 754二重浮動小数点値:あなたは、整数精度で2⁵³(9007199254740992)まで格納できるように、あなたは、仮数の53ビットを得ます。それを超えると、他の値に丸められます(したがって、9007199254740993 === 9007199254740992)。999999999999999999999999は1000000000000000000000に丸められ、デフォルトのtoString表現は1e+21です。
ボビンス

22

または、jsonを使用してエンコードしているときに、辞書を[(k1、v1)、(k2、v2)]形式のリストに変換し、デコードしてから辞書に戻すこともできます。


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

この関数はネストされたdictでも機能し、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に変換します


これは素晴らしかった。私の場合、酸洗いは使用できないので、圧縮を使用できるように、byte_arrayへの変換によってJSONを使用してオブジェクトの根性を保存しています。混合キーがあるので、キーをintに変換できない場合はValueErrorを無視するように例を変更しました
minillinim

11

私は同じ問題に噛まれました。他の人が指摘したように、JSONでは、マッピングキーは文字列である必要があります。2つのことの1つを行うことができます。整数文字列を許可するdemjsonなど、それほど厳密ではないJSONライブラリを使用できます。他のプログラム(または他の言語では他のプログラム)がそれを読み取らない場合は、問題ありません。または、別のシリアル化言語を使用することもできます。ピクルスはお勧めしません。読みにくく、安全に設計されていません。代わりに、JSONの(ほぼ)スーパーセットであり、整数キーを許可するYAMLをお勧めします。(少なくとも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'}}

intへのjsonキーを解析するためだけのフィルターがあります。int(v) if v.lstrip('-').isdigit() else vjson値のフィルターも使用できます。


1

Murmelの回答の非常に単純な拡張を作成しました。これは、JSONによって最初にダンプできると想定して、かなり任意の辞書(ネストされたものを含む)で機能すると思います。整数として解釈できるキーはすべてintにキャストされます。確かにこれはあまり効率的ではありませんが、json文字列に格納したり、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のすべてのキーが整数にキャストできる場合は整数であると想定すると、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.