JSONからUnicodeではなく文字列オブジェクトを取得する方法


276

Python 2を使用して、ASCIIエンコードされたテキストファイルからJSONを解析しています。

jsonまたは simplejsonでこれらのファイルをロードすると、すべての文字列値が文字列オブジェクトではなくUnicodeオブジェクトにキャストされます。問題は、文字列オブジェクトのみを受け入れる一部のライブラリでデータを使用する必要があることです。私は、ライブラリを変更することはできませんもそれらを更新します。

Unicodeオブジェクトの代わりに文字列オブジェクトを取得することは可能ですか?

>>> import json
>>> original_list = ['a', 'b']
>>> json_list = json.dumps(original_list)
>>> json_list
'["a", "b"]'
>>> new_list = json.loads(json_list)
>>> new_list
[u'a', u'b']  # I want these to be of type `str`, not `unicode`

更新

この質問は、Python 2に悩まされていた昔のことです。今日の簡単でクリーンなソリューションの1つは、Pythonの最新バージョン、つまりPython 3以降を使用することです。


1
Python3では問題はありません。new_listの項目のタイプはstr
GoingMyWay 2017年

1
Python 3kは「Pythonの最新バージョン」ではなく、単なる代替ブランチです。
user2589273 2017

11
Pythonの2が廃止され、何のメンテナンスが2年未満である2020年1月1日、後に起こりません-それは、2017年12月に、このようなコメントを見て奇妙だ:pythonclock.org
Zaarハイ

1
@ZaarHai多くの人がPython 2で意に反して立ち往生しています。自動化とスクリプティングのために独自のPythonバージョンを組み込んだ多くのアプリケーションがあるので、ベンダーの更新まで私はそれを使用する必要があります(私はMaya、Houdini、Nukeを探しています)
Geordie

1
@Geordie私は確かにそれを知っていて理解しています。私のコメントは用語に関するものでした-Pythonは「代替ブランチ」ではなく、それに悩まされている人たちにとっては残念ながら代替(プン意図)の欠如です。
Zaar Hai

回答:


101

ソリューション object_hook

import json

def json_load_byteified(file_handle):
    return _byteify(
        json.load(file_handle, object_hook=_byteify),
        ignore_dicts=True
    )

def json_loads_byteified(json_text):
    return _byteify(
        json.loads(json_text, object_hook=_byteify),
        ignore_dicts=True
    )

def _byteify(data, ignore_dicts = False):
    # if this is a unicode string, return its string representation
    if isinstance(data, unicode):
        return data.encode('utf-8')
    # if this is a list of values, return list of byteified values
    if isinstance(data, list):
        return [ _byteify(item, ignore_dicts=True) for item in data ]
    # if this is a dictionary, return dictionary of byteified keys and values
    # but only if we haven't already byteified it
    if isinstance(data, dict) and not ignore_dicts:
        return {
            _byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True)
            for key, value in data.iteritems()
        }
    # if it's anything else, return it in its original form
    return data

使用例:

>>> json_loads_byteified('{"Hello": "World"}')
{'Hello': 'World'}
>>> json_loads_byteified('"I am a top-level string"')
'I am a top-level string'
>>> json_loads_byteified('7')
7
>>> json_loads_byteified('["I am inside a list"]')
['I am inside a list']
>>> json_loads_byteified('[[[[[[[["I am inside a big nest of lists"]]]]]]]]')
[[[[[[[['I am inside a big nest of lists']]]]]]]]
>>> json_loads_byteified('{"foo": "bar", "things": [7, {"qux": "baz", "moo": {"cow": ["milk"]}}]}')
{'things': [7, {'qux': 'baz', 'moo': {'cow': ['milk']}}], 'foo': 'bar'}
>>> json_load_byteified(open('somefile.json'))
{'more json': 'from a file'}

これはどのように機能し、なぜそれを使用するのですか?

マーク・アメリーの機能はこれらのものより短くて明確です、それでそれらのポイントは何ですか?なぜそれらを使いたいのですか?

純粋にパフォーマンスのため。Markの答えは、最初にJSONテキストを完全にUnicode文字列でデコードし、次にデコードされた値全体を再帰してすべての文字列をバイト文字列に変換します。これには、いくつかの望ましくない影響があります。

  • デコードされた構造全体のコピーがメモリに作成されます
  • JSONオブジェクトが本当に深くネストされている場合(500レベル以上)、Pythonの最大再帰深度に達します

この回答object_hookではjson.load、およびのパラメーターを使用することにより、これらの両方のパフォーマンスの問題を軽減していますjson.loadsドキュメントから:

object_hookデコードされたオブジェクトリテラル(a dict)の結果で呼び出されるオプションの関数です。の代わりにobject_hookの戻り値が使用されdictます。この機能を使用して、カスタムデコーダーを実装できます。

他のディクショナリの深い階層にネストされたディクショナリobject_hook は、デコードされるときに渡されるため、その時点でそれらの内部の文字列またはリストをバイト化し、後で深い再帰を行う必要をなくすことができます。

マークの答えはobject_hook、ネストされた辞書に再帰するため、現状のままでは適切ではありません。この回答では、へのignore_dictsパラメーターを使用して再帰を防止しています。このパラメーターは_byteify、byteifyに新しいパラメーターを渡す場合を除いて、常に渡されます。フラグが伝え無視して、彼らはすでにbyteifiedされて以来秒。object_hookdictignore_dicts_byteifydict

最後に、デコードされたJSONテキストのトップレベルにaがない場合を処理するために、またはから返された結果を(で)実装しjson_load_byteifiedjson_loads_byteified呼び出します。_byteifyignore_dicts=Truejson.loadjson.loadsdict


1
ここでのアプローチの+1。初めて読んだときはあまり理解できませんでしたが、トラビスジェンセンの答えを踏まえて、もう一度読んだときはようやくわかりました。私はそれがどのように機能し、私の答えに対する利点が何であるかを明確にすることを期待してかなり積極的な編集を行いました。コードの中核となるアイデアは変更されていませんが、他のほとんどすべてを変更しました。これに反対する場合は、編集内容を自由にロールバックしてください。それがあなたの答えです!
Mark Amery 2016年

問題ありませんマーク、ありがとうございます 私はあなたの編集が好きです、それは私のオリジナルよりもはるかに説明的です。たぶん、いつかもっと簡潔な答えを出すことを学びます。
Mirec Miskuf、2016年

2
これは素晴らしい解決策です。効率的でエレガント。:あなたは、Python <2.7の領域で立ち往生している場合私はしかし、あなたは、ラインを交換する必要がありますreturn { byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True) for key, value in data.iteritems() }return dict((_byteify(key, ignore_dicts=True), _byteify(value, ignore_dicts=True)) for key, value in data.iteritems())、それのための仕事に。
Richard Dunn

再帰の深さの問題についてはあなたが間違っていると思います。あなたのもので、私は990まで行くことができます:json_loads_byteified('[' * 990 + ']' * 990)。991ではクラッシュします。Markは991でも動作しますbyteify(json.loads('[' * 991 + ']' * 991))。992でクラッシュします。したがって、少なくともこのテストでは、Markは、あなたが言ったこととは逆に、より深く行くことができます。
ステファンポックマン2017年

@MarkAmery上記のコメントについてどう思いますか?(私が編集履歴で見たのは、その主張を追加したのは実際にはあなただった)
ステファンポックマン2017年

180

ここにはいくつかの良い答えがありますが、キーと値をタイプではなくタイプ文字列として提供するため、私は最終的にPyYAMLを使用してJSONファイルを解析しました。JSONはYAMLのサブセットであるため、うまく機能します。strunicode

>>> import json
>>> import yaml
>>> list_org = ['a', 'b']
>>> list_dump = json.dumps(list_org)
>>> list_dump
'["a", "b"]'
>>> json.loads(list_dump)
[u'a', u'b']
>>> yaml.safe_load(list_dump)
['a', 'b']

ノート

ただし、次の点に注意してください。

  • すべてのエントリがASCIIエンコードされているため、文字列オブジェクトを取得します。Unicodeでエンコードされたエントリを使用すると、Unicodeオブジェクトとして返されます —変換はありません!

  • PyYAMLのsafe_load関数を(おそらく常に)使用する必要があります。JSONファイルをロードするためにそれを使用する場合、loadとにかく関数の「追加のパワー」は必要ありません。

  • あなたは(と仕様のバージョン1.2のためのより多くのサポートを持っているYAMLパーサたい場合は、非常に低い数字を正しく解析する)してみてくださいRuamel YAMLをpip install ruamel.yamlimport ruamel.yaml as yaml私のテストに必要なすべての私がいました。

変換

述べたように、変換はありません!ASCII値のみを処理することが確実ではない場合(そして、ほとんどの場合確信が持てない場合)、変換関数を使用することをお勧めします。

私はMark Ameryのものを数回使用しましたが、うまく機能し、非常に使いやすいです。object_hook大きなファイルのパフォーマンスが向上する可能性があるため、代わりに同様の関数を使用することもできます。これについては、Mirec Miskufのもう少し複雑な回答を参照してください。


8
この回答を使用する場合は、少し注意してください。これはブルータスの場合に完全に機能しますが、彼のデータにはASCIIエンコード可能な文字しか含まれていないことがわかっているためです。その保証がない場合、この答えは機能しません。たとえばyaml.load(json.dumps([u'a', u'£', u'É']))、Pythonシェルで実行してみて、文字列['a', u'\xa3', u'\xc9']が含まれていることを確認しますunicode。データにASCII文字セットの文字のみが含まれていることが確実でない場合は、代わりに別の方法を使用する必要があります(自分の回答をお勧めします)。
マークアメリー2014

1
YAMLでも[u'a', u'b']注意が必要です。
Carlos Calla 14

1
これはいいですが、それはここ..低い数字で見て動作しません:stackoverflow.com/questions/30458977/...
オレン

@オーレン:これはYAML仕様のエラーではなく、PyYAMLパーサーのエラーです。ruamelYAMLパーサーが機能します。
Brutus

["a"、 "b"]ではなく["a"、 "b"]のように出力したい@Brutus
user60679

141

jsonモジュール関数がUnicode文字列ではなくバイト文字列を返すようにする組み込みオプションはありません。ただし、この短く単純な再帰関数は、デコードされたJSONオブジェクトをUnicode文字列の使用からUTF-8エンコードのバイト文字列に変換します。

def byteify(input):
    if isinstance(input, dict):
        return {byteify(key): byteify(value)
                for key, value in input.iteritems()}
    elif isinstance(input, list):
        return [byteify(element) for element in input]
    elif isinstance(input, unicode):
        return input.encode('utf-8')
    else:
        return input

json.loadまたはjson.loads呼び出しから取得した出力でこれを呼び出すだけです。

いくつかのメモ:

  • Pythonの2.6またはそれ以前のバージョンをサポートするために、交換するreturn {byteify(key): byteify(value) for key, value in input.iteritems()}return dict([(byteify(key), byteify(value)) for key, value in input.iteritems()])辞書内包表記はPython 2.7まではサポートされていなかったことから、。
  • この回答はデコードされたオブジェクト全体で再帰するため、object_hookor object_pairs_hookパラメータを非常に慎重に使用することで回避できる、いくつかの望ましくないパフォーマンス特性があります。これまでのところ、Mirec Miskufの答えは、これを正しく管理できた唯一の回答ですが、結果として、私のアプローチよりもはるかに複雑になっています。

1
私はこれが好きです-無視ではありません-「文字列」や「ascii」と言うとき、彼らは理論的にはユニコード文字ではなく、単純にバイトが欲しかったということを認識しています。(ASCIIではなく、もう一方の端にポンド記号が必要なため)
Danny Staple

私はこれが好きです。jsonがタプルを作成しないことを知っているので、それは私のかわいいプリンターが機能するのとほぼ同じように機能します。タプルの例外も追加する必要があります。
y.petremann 2015

これは恐ろしく非効率的であり、必要のないノードを再帰的にトラバースする必要があります。jsonモジュールは、これをより効率的に行うためのフックを提供します。以下の使用の答えobject_hookは実際にはこれよりもはるかに悪いですが、を使用object_pairs_hookすると、文字列を含まないノードの再帰や再訪問を必要としない、かなり効率的な方法を思い付くことができます。
Travis Jensen

1
@TravisJensen興味深い。このobject_pairs_hook方法はおそらくこれよりも少し理解するのが難しいです(パラメーターのしくみと、リストとディクテーションが異なる処理を必要とする理由を理解する必要があります)。パフォーマンスの利点はほとんどの人にとって重要ではありません...しかし、私は期待します特に異常に深くネストされたJSONオブジェクトを扱う人にとっては存在します。
Mark Amery

plus1これは最も簡潔な答えです。PyYAMLに加えて、インストールが面倒です。4Xメモリを使用しないように変換をマイクロストリーム化するのが唯一の方法です。
personal_cloud

74

object_hookパラメータを使用json.loadsしてコンバータに渡すことができます。事後に変換を行う必要はありません。jsonモジュールは常に合格するobject_hookだけdictsを、そしてあなたは、ネストされたdictsに自分自身を再帰的にする必要はありませんので、それは再帰的に、ネストされたdictsに渡します。Wellsのショーのように、Unicode文字列を数値に変換するとは思いません。Unicode文字列の場合、JSONファイルでは文字列として引用されているため、文字列であると想定されます(またはファイルが不良です)。

また、私のような何かやって回避しようと思いますstr(val)上のunicodeオブジェクトを。value.encode(encoding)外部libが期待するものに応じて、有効なエンコーディングで使用する必要があります。

したがって、たとえば:

def _decode_list(data):
    rv = []
    for item in data:
        if isinstance(item, unicode):
            item = item.encode('utf-8')
        elif isinstance(item, list):
            item = _decode_list(item)
        elif isinstance(item, dict):
            item = _decode_dict(item)
        rv.append(item)
    return rv

def _decode_dict(data):
    rv = {}
    for key, value in data.iteritems():
        if isinstance(key, unicode):
            key = key.encode('utf-8')
        if isinstance(value, unicode):
            value = value.encode('utf-8')
        elif isinstance(value, list):
            value = _decode_list(value)
        elif isinstance(value, dict):
            value = _decode_dict(value)
        rv[key] = value
    return rv

obj = json.loads(s, object_hook=_decode_dict)

3
のオブジェクトsがJSON Object(キーと値を区切る ':'文字を含む、キーと値のペアの順序付けられていないコレクションであり、コンマで区切られ、中かっこで囲まれている)の場合は問題ありませんが、たとえば、 JSON Array。したがって、JSON ArrayなどのJSONを指定して["a", "b"]も、結果は変わりません[u'a', u'b']。の他の現在利用可能なカスタマイズフックタイプのパラメーターjson.loads()も、ジョブを実行できません。
マーティ

2
あなたが述べたように、jsonモジュールはネストされたを再帰的に渡すdictので、2つの関数elifでそれらをチェックする必要はありません-したがって、それらをチェックする2つの句を削除する必要があります。
martineau

1
アンダースコアで始まる関数名は、importステートメントにとって特別な意味を持つことに注意してください。これらの関数をUtility.pyというファイルと別のファイルdo from Utility import *に配置すると、そのアンダースコアのために関数は表示されません
M Katz 2013年

1
これは本当に悪い考えです。object_hookパースされたすべてのjsonオブジェクトに対して呼び出されるので、与えられたものに再帰すると、すでに「バイト化」したことを「バイト化」することになります。オブジェクトのサイズに応じて、パフォーマンスは幾何学的に成長します。ここではobject_pairs_hook、その問題を使用し、その問題に悩まされない回答を記載しました。
Travis Jensen

38

これは、jsonが文字列オブジェクトとUnicodeオブジェクトの間に違いがないためです。それらはすべてJavaScriptの文字列です。

JSONはUnicodeオブジェクトを返すのに適していると思います。実際、JavaScript文字列は実際にはunicodeオブジェクト(つまり、JSON(JavaScript)文字列はあらゆる種類のUnicode 文字を格納できる)であるため、これ以上何も受け入れませんunicode。JSONから文字列を変換するときにオブジェクトを作成するのは理にかなっています。ライブラリは必要なエンコーディングを推測する必要があるため、プレーンな文字列は適合しません。

unicodeどこでも文字列オブジェクトを使用することをお勧めします。したがって、ライブラリを更新してUnicodeオブジェクトを処理できるようにするのが最善の方法です。

ただし、バイト文字列が本当に必要な場合は、結果を任意のエンコーディングにエンコードするだけです。

>>> nl = json.loads(js)
>>> nl
[u'a', u'b']
>>> nl = [s.encode('utf-8') for s in nl]
>>> nl
['a', 'b']

noskloに感謝します。それが私が最初に行ったことです。しかし、言ったように、私が使用した実際のデータはかなり入れ子になっているため、静かにオーバーヘッドが発生しました。私はまだ自動解決策を探しています...少なくとも1つのバグレポートがあり、simplejsonがUnicodeではなく文字列オブジェクトを返すことについて不平を言っています。
Brutus

1
@Brutus:jsonがUnicodeオブジェクトを返すのは正しいと思います。実際、javascript文字列は実際にはユニコードオブジェクトであるため、これ以上は受け入れません。つまり、json(javascript)文字列はあらゆる種類のUnicode文字を格納できるため、jsonから変換するときにUnicodeオブジェクトを作成することは理にかなっています。代わりにライブラリを実際に修正する必要があります。
nosklo

16

簡単な回避策があります。

TL; DR-のast.literal_eval()代わりに使用しますjson.loads()astjsonは両方とも標準ライブラリにあります。

「完全な」答えではありませんが、Unicodeを完全に無視するという計画の場合、かなり遠くまで届きます。Python 2.7の場合

import json, ast
d = { 'field' : 'value' }
print "JSON Fail: ", json.loads(json.dumps(d))
print "AST Win:", ast.literal_eval(json.dumps(d))

与える:

JSON Fail:  {u'field': u'value'}
AST Win: {'field': 'value'}

一部のオブジェクトが実際にUnicode文字列である場合、これはさらに複雑になります。完全な答えはすぐにわかりにくくなります。


11
ベターは必ずあなたのJSONはどの含まれていないことnulltrueまたはfalse、彼らはpythonで有効でないと発生しますので、値をliteral_eval()失敗します。
ʇsәɹoɈ

3
@ʇsәɹoɈまた、JSONに\/文字列内のエスケープされたsolidus()やユニコードエスケープシーケンス(のような"\u0061"別の記述方法)が含まれていないことを期待してください"a"。Pythonのリテラル構文は、いくつかの点でJSONと互換性がありません。私が捨てるつもりのなかったスクリプトについては、この答えは信用できません。
Mark Amery、2015

文字列が本当にユニコードである場合、この答えは失敗しますが、それが事実である場合、とにかく文字列にキャストすることはできません。+1が機能する場合にのみ機能し、それ以外の場合は例外をスローする回答
ステファンサリバン

可能であればjson、データのダンプに使用せず、printPythonを実行している場合にのみ使用してください。そして、ast.literal_eval作品
ジャン=フランソワ・ファーブル

11

Mike Brennanの答えは近いですが、構造全体を再走査する理由はありません。object_hook_pairs(Python 2.7+)パラメータを使用する場合:

object_pairs_hookペアの順序付きリストでデコードされたオブジェクトリテラルの結果で呼び出されるオプションの関数です。の戻り値がのobject_pairs_hook代わりに使用されdictます。この機能を使用して、キーと値のペアがデコードされる順序に依存するカスタムデコーダーを実装できます(たとえば、collections.OrderedDict挿入の順序を記憶します)。object_hookも定義されている場合は、object_pairs_hookが優先されます。

これにより、各JSONオブジェクトが渡されるので、再帰を必要とせずにデコードを実行できます。

def deunicodify_hook(pairs):
    new_pairs = []
    for key, value in pairs:
        if isinstance(value, unicode):
            value = value.encode('utf-8')
        if isinstance(key, unicode):
            key = key.encode('utf-8')
        new_pairs.append((key, value))
    return dict(new_pairs)

In [52]: open('test.json').read()
Out[52]: '{"1": "hello", "abc": [1, 2, 3], "def": {"hi": "mom"}, "boo": [1, "hi", "moo", {"5": "some"}]}'                                        

In [53]: json.load(open('test.json'))
Out[53]: 
{u'1': u'hello',
 u'abc': [1, 2, 3],
 u'boo': [1, u'hi', u'moo', {u'5': u'some'}],
 u'def': {u'hi': u'mom'}}

In [54]: json.load(open('test.json'), object_pairs_hook=deunicodify_hook)
Out[54]: 
{'1': 'hello',
 'abc': [1, 2, 3],
 'boo': [1, 'hi', 'moo', {'5': 'some'}],
 'def': {'hi': 'mom'}}

を使用するとすべてのオブジェクトがフックに渡されるため、フックを再帰的に呼び出す必要がないことに注意してくださいobject_pairs_hook。リストを気にする必要はありますが、ご覧のとおり、リスト内のオブジェクトは適切に変換され、それを実行するために再帰する必要はありません。

編集:同僚は、Python2.6にはがないことを指摘しましたobject_hook_pairs。非常に小さな変更を加えるだけで、Python2.6を使用できます。上記のフックで、次のように変更します。

for key, value in pairs:

for key, value in pairs.iteritems():

次にのobject_hook代わりに使用しますobject_pairs_hook

In [66]: json.load(open('test.json'), object_hook=deunicodify_hook)
Out[66]: 
{'1': 'hello',
 'abc': [1, 2, 3],
 'boo': [1, 'hi', 'moo', {'5': 'some'}],
 'def': {'hi': 'mom'}}

object_pairs_hook結果を使用すると、JSONオブジェクトの各オブジェクトに対してインスタンス化される辞書が1つ少なくなります。これは、巨大なドキュメントを解析している場合、価値があるかもしれません。


1
これはすっきりしていて、緑色のチェックマークに値するものに非常に近いようです(ブルータスは、見事に、より良い答えが出てきたので、すでに寛大に渡っています)。しかし... deunicodify_hookこの回答で示したで実際にリストを適切に処理しないのはなぜですか?現時点では、deunicodify_hookリストを反復処理せず、文字列とリスト内のリストを非ユニコード化する実装があるため、表示されている出力は、フックが実際に生成する出力と一致しませ。それを修正すれば、この答えは私のものより優れたものになるでしょう。
Mark Amery 2016年

軽薄:ここで使用しているもの(IronPythonと思います)ではなく、通常のCPythonインタープリターで関数をデモンストレーションすることもお勧めしますか?CPythonインタープリターは、ほとんどのPythonユーザーに馴染みがあり、私の意見ではよりきれいです。
Mark Amery 2016年

これは私にとっては機能しませんが、私がやっていることの奇妙なことだと確信しています...より大きなjsonドキュメントから1つのリストをファイルに保存しています。このobject_pairs_hookの有無にかかわらず、それをロードするかどうかにかかわらず、すべてのアイテムがユニコードで表示されます。くそー。
rsaw

1
@rsaw良い点!以来object_pairs_hookだけのために呼び出されるオブジェクトあなたのJSONテキストがトップレベルでの文字列のリストを持っている場合は、この解決策は失敗します。から返されたものに対して関数を呼び出さずにこれを修正する方法はありませんjson.load。いずれjson.loadフックを使用すると、すべての文字列を扱うことができるようになります保証することはできません。これは、フックの使用よりも私のソリューションを推奨し続けるのに十分なほど大きな欠陥だと思います。
Mark Amery 2016年

-1私は、Mirec Miskufがすでに Mike Brennanのアプローチ(同じ辞書を複数回バイト化する)の欠点も、ネストされたリストやトップレベルリストのバイト化に失敗する欠点もないオブジェクトフックの回答を投稿したことに気付いたからまたは文字列)。これは劣っているが、急速に票を獲得しているのに、なぜ彼の答えがほとんど注目されずに衰退したのかはわからない。
Mark Amery 2016年

9

simplejsonライブラリ内でこれを自動的に達成する方法はないと思います。

simplejsonのスキャナーとデコーダーは、Unicodeテキストを生成するように設計されています。これを行うために、ライブラリはc_scanstring(使用可能な場合は、速度を上げるために)と呼ばれる関数を使用するpy_scanstringか、Cバージョンが使用できない場合に使用します。scanstring関数はのsimplejsonにテキストが含まれている可能性がある構造を復号するための持っているほぼすべてのルーチンで数回呼ばれています。simplejson.decoderのscanstring値をサルパッチするか、サブクラスJSONDecoderを作成して、テキストを含む可能性のあるものの独自の実装全体を提供する必要があります。

ただし、simplejsonがUnicodeを出力するのは、jsonの仕様に「文字列は0個以上のUnicode文字のコレクションである」と明記されているためです... Unicodeのサポートは、フォーマット自体の一部と見なされます。Simplejsonのscanstring実装は、Unicodeエスケープのスキャンと解釈にまで及んでいるため(不正なマルチバイト文字セット表現のエラーチェックでも)、確実に値を返す唯一の方法は、Unicodeとしてです。

を必要とする古くなったライブラリがある場合は、str解析後に入れ子になったデータ構造を手間をかけて検索するか(申し訳ありませんが、申し訳ありませんが...)より詳細なレベルで入力パラメーターをマッサージできるファサード。データ構造が実際に深くネストされている場合、2番目のアプローチは最初のアプローチよりも管理しやすくなる可能性があります。


4

Mark(Amery)が正しく注記しているように、jsonダンプでPyYamlのデシリアライザを使用できるのは、ASCIIしか使用できない場合のみです。少なくとも箱から出して。

PyYamlアプローチに関する2つの簡単なコメント:

  1. NEVERフィールドからのデータのyaml.loadを使用していません。構造内に隠された任意のコードを実行するためのyamlの機能(!)

  2. 次のようにして、非ASCIIでも機能させることができます。

    def to_utf8(loader, node):
        return loader.construct_scalar(node).encode('utf-8')
    yaml.add_constructor(u'tag:yaml.org,2002:str', to_utf8)

しかし、パフォーマンスに関しては、マークアメリーの回答とは比較になりません。

2つのメソッドに深くネストされたサンプル辞書をいくつか投げると、これが得られます(dt [j] = json.loads(json.dumps(m)の時間デルタ)):

     dt[yaml.safe_load(json.dumps(m))] =~ 100 * dt[j]
     dt[byteify recursion(Mark Amery)] =~   5 * dt[j]

したがって、jsonのCベースの実装とほぼ同じ大きさで、ツリーの完全なウォーキングエンコーディングを含む逆シリアル化を行います。これは非常に高速で、深くネストされた構造でのyamlロードよりも堅牢であることがわかります。yaml.loadを見ると、セキュリティエラーが発生しにくくなります。

=> Cのみに基づくコンバーターへのポインターに感謝しますが、byteify関数がデフォルトの答えになるはずです。

これは、json構造がユーザー入力を含むフィールドからのものである場合に特に当てはまります。おそらく、とにかくあなたの構造の上を歩く必要があるからです -希望する内部データ構造(「ユニコードサンドイッチ」またはバイト文字列のみ)とは無関係です。

どうして?

Unicodeの正規化。気づかない場合:鎮痛剤を服用してこれを読んでください。

したがって、byteify再帰を使用して、1石で2羽の鳥を殺します。

  1. ネストされたjsonダンプからバイト文字列を取得する
  2. 正規化されたユーザー入力値を取得して、ストレージにあるものを見つけます。

私のテストでは、input.encode( 'utf-8')をunicodedata.normalize( 'NFC'、input).encode( 'utf-8')で置き換えることは、NFCを使用しない場合よりもさらに高速であることがわかりましたが、それは私が推測するサンプルデータに大きく依存しています。


3

落とし穴があることsimplejsonjson、少なくともそれらがユニコードを扱うようにして、二つの異なるモジュールです。あなたはjsonpy 2.6+を持っています、そしてこれはあなたにユニコード値を与えますsimplejsonが、文字列オブジェクトを返します。環境でeasy_install-ing simplejsonを試してみて、それが機能するかどうかを確認してください。それは私のためにした。


2

ダンプとロードには、jsonの代わりにpickleを使用するだけです。

    import json
    import pickle

    d = { 'field1': 'value1', 'field2': 2, }

    json.dump(d,open("testjson.txt","w"))

    print json.load(open("testjson.txt","r"))

    pickle.dump(d,open("testpickle.txt","w"))

    print pickle.load(open("testpickle.txt","r"))

それが生成する出力は次のとおりです(文字列と整数は正しく処理されます):

    {u'field2': 2, u'field1': u'value1'}
    {'field2': 2, 'field1': 'value1'}

1
追加のパッケージ(yamlなど)を必要としないソリューションの場合は+1 。しかし、時々-私の元のケースのように-私はデータをJSONにする必要があるので、ピクルスが常に最良のオプションであるとは限りません。その上、あなたはsafe_loadYAMLで持っています、ピクルスに同様のものが存在するかどうかはわかりません。
Brutus 14

1

だから、私は同じ問題に遭遇しました。最初のGoogleの結果を推測してください。

すべてのデータをPyGTKに渡す必要があるため、Unicode文字列もあまり役に立ちません。だから私は別の再帰的な変換方法を持っています。タイプセーフなJSON変換にも実際に必要です。json.dump()は、Pythonオブジェクトのような非リテラルをベイルします。ただし、dictインデックスは変換しません。

# removes any objects, turns unicode back into str
def filter_data(obj):
        if type(obj) in (int, float, str, bool):
                return obj
        elif type(obj) == unicode:
                return str(obj)
        elif type(obj) in (list, tuple, set):
                obj = list(obj)
                for i,v in enumerate(obj):
                        obj[i] = filter_data(v)
        elif type(obj) == dict:
                for i,v in obj.iteritems():
                        obj[i] = filter_data(v)
        else:
                print "invalid object in data, converting to string"
                obj = str(obj) 
        return obj

ここで発生する可能性がある唯一の問題は、Unicodeから変換された辞書のキーが必要な場合です。この実装は値を変換しますが、Unicodeキーを維持します。「newobj」を作成し、newobj [str(i)] = ...を使用し、完了時にobj = newobjを割り当てると、キーも変換されます。
Neal Stublen、2010

キーを変換することで、理解度が高くなるか、より良くなる可能性があります。また、慣用的です。それは両方の場所でオブジェクトを変更し(ディクショナリの場合)、新しい値を返します。これは、現在のオブジェクトを変更するか、または新しいオブジェクトを返すPythonの組み込みコレクションメソッドと矛盾しますが、両方ではありません。
Mark Amery、2015

1

文字列としてJSON dictがありました。キーと値は、次の例のようなUnicodeオブジェクトでした。

myStringDict = "{u'key':u'value'}"

私は使うことができました byteify上記関数をて文字列をdictオブジェクトに変換しますast.literal_eval(myStringDict)


あなたが与えた例はJSONの例ではありません。{u'key':u'value'}JSONではありません。
Mark Amery、2015

2
私はそれがJSONではないことを完全に知っています。これが、Pythonスクリプトで外部ソースから解析された方法です。次の例のように直接JSONである場合、ソリューションとしてマークされているbyteify関数は必要ありません:{"firstName": "John"、 "lastName": "Doe"}。投票する前に回答を読んでいただければ幸いです。ありがとう。
narko

1

フックを使用してPython2&3をサポートしますhttps://stackoverflow.com/a/33571117/558397から)

import requests
import six
from six import iteritems

requests.packages.urllib3.disable_warnings()  # @UndefinedVariable
r = requests.get("http://echo.jsontest.com/key/value/one/two/three", verify=False)

def _byteify(data):
    # if this is a unicode string, return its string representation
    if isinstance(data, six.string_types):
        return str(data.encode('utf-8').decode())

    # if this is a list of values, return list of byteified values
    if isinstance(data, list):
        return [ _byteify(item) for item in data ]

    # if this is a dictionary, return dictionary of byteified keys and values
    # but only if we haven't already byteified it
    if isinstance(data, dict):
        return {
            _byteify(key): _byteify(value) for key, value in iteritems(data)
        }
    # if it's anything else, return it in its original form
    return data

w = r.json(object_hook=_byteify)
print(w)

戻り値:

 {'three': '', 'key': 'value', 'one': 'two'}

0

これはゲームに遅れますが、私はこの再帰キャスターを作成しました。それは私のニーズに合っており、比較的完成していると思います。それはあなたを助けるかもしれません。

def _parseJSON(self, obj):
    newobj = {}

    for key, value in obj.iteritems():
        key = str(key)

        if isinstance(value, dict):
            newobj[key] = self._parseJSON(value)
        elif isinstance(value, list):
            if key not in newobj:
                newobj[key] = []
                for i in value:
                    newobj[key].append(self._parseJSON(i))
        elif isinstance(value, unicode):
            val = str(value)
            if val.isdigit():
                val = int(val)
            else:
                try:
                    val = float(val)
                except ValueError:
                    val = str(val)
            newobj[key] = val

    return newobj

次のようにJSONオブジェクトを渡すだけです。

obj = json.loads(content, parse_float=float, parse_int=int)
obj = _parseJSON(obj)

クラスのプライベートメンバーとして持っていますが、必要に応じてメソッドを再利用できます。


JSONを解析し、結果のマッピングを** kwargsとして関数に渡そうとする問題に遭遇しました。関数のパラメーター名をUnicodeにすることはできないため、_parseJSON関数は優れています。もっと簡単な方法があれば、誰かに知らせてもらえます。
Neal Stublen、2010

1
このコードには問題があります-リストの一部で再帰呼び出しを行いますが、リストの要素自体が辞書でない場合は失敗します。
I8210月

@ I82Muchで説明されているバグに加えて、これも不適切な名前が付けられており(実際にはJSONを解析しません。json.loads最初に呼び出しが必要です)、説明のない理由で任意に文字列をintに変換しようとしますが、コピーおよび-貼り付けます。
Mark Amery、2015

0

Wellsの_parse_json()を書き直して、jsonオブジェクト自体が配列である場合(私の使用例)を処理しました。

def _parseJSON(self, obj):
    if isinstance(obj, dict):
        newobj = {}
        for key, value in obj.iteritems():
            key = str(key)
            newobj[key] = self._parseJSON(value)
    elif isinstance(obj, list):
        newobj = []
        for value in obj:
            newobj.append(self._parseJSON(value))
    elif isinstance(obj, unicode):
        newobj = str(obj)
    else:
        newobj = obj
    return newobj

0

これはCで書かれた再帰的なエンコーダです:https : //github.com/axiros/nested_encode

json.loadsと比較して、「平均的な」構造のパフォーマンスオーバーヘッドは約10%です。

python speed.py                                                                                            
  json loads            [0.16sec]: {u'a': [{u'b': [[1, 2, [u'\xd6ster..
  json loads + encoding [0.18sec]: {'a': [{'b': [[1, 2, ['\xc3\x96ster.
  time overhead in percent: 9%

このテスト構造を使用して:

import json, nested_encode, time

s = """
{
  "firstName": "Jos\\u0301",
  "lastName": "Smith",
  "isAlive": true,
  "age": 25,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "\\u00d6sterreich",
    "state": "NY",
    "postalCode": "10021-3100"
  },
  "phoneNumbers": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "office",
      "number": "646 555-4567"
    }
  ],
  "children": [],
  "spouse": null,
  "a": [{"b": [[1, 2, ["\\u00d6sterreich"]]]}]
}
"""


t1 = time.time()
for i in xrange(10000):
    u = json.loads(s)
dt_json = time.time() - t1

t1 = time.time()
for i in xrange(10000):
    b = nested_encode.encode_nested(json.loads(s))
dt_json_enc = time.time() - t1

print "json loads            [%.2fsec]: %s..." % (dt_json, str(u)[:20])
print "json loads + encoding [%.2fsec]: %s..." % (dt_json_enc, str(b)[:20])

print "time overhead in percent: %i%%"  % (100 * (dt_json_enc - dt_json)/dt_json)

0

Python 3.6では、まだこの問題が発生することがあります。たとえば、REST APIから応答を取得し、応答テキストをJSONにロードしても、Unicode文字列を取得します。json.dumps()を使用した簡単な解決策が見つかりました。

response_message = json.loads(json.dumps(response.text))
print(response_message)

-1

私もこの問題に遭遇し、JSONを処理する必要があるため、Unicodeキーを文字列に変換する小さなループを思いつきました。(simplejsonGAEでは文字列キーを返しません。)

obj JSONからデコードされたオブジェクトです。

if NAME_CLASS_MAP.has_key(cls):
    kwargs = {}
    for i in obj.keys():
        kwargs[str(i)] = obj[i]
    o = NAME_CLASS_MAP[cls](**kwargs)
    o.save()

kwargsGAEアプリケーションのコンストラクターに渡すものです(これはのunicodeキーが好きではありません**kwargs)。

Wellsのソリューションほど堅牢ではありませんが、はるかに小さいです。


-1

私はからコード適応してきた答えマークアメリーを特にを取り除くために、isinstanceダックタイピングの長所のために。

エンコードは手動で行われ、ensure_ascii無効になっています。のPythonドキュメントjson.dumpはそれを言っています

Ensure_asciiがTrue(デフォルト)の場合、出力内のすべての非ASCII文字は\ uXXXXシーケンスでエスケープされます

免責事項:doctestでは、ハンガリー語を使用しました。いくつかの注目すべきハンガリー関連の文字エンコーディングは、次のとおりcp852です。DOSで(asciiと呼ばれることもありますが、誤ってコードページ設定に依存していると思います)cp1250。Windowsでは(ロケール設定に応じてansiと呼ばれることもあります)、iso-8859-2httpサーバーで使用されることもあります。テストテキストTüskéshátú kígyóbűvölőKoltaiLászló(ネイティブの個人名フォーム)によるもので、ウィキペディアからのものです。

# coding: utf-8
"""
This file should be encoded correctly with utf-8.
"""
import json

def encode_items(input, encoding='utf-8'):
    u"""original from: https://stackoverflow.com/a/13101776/611007
    adapted by SO/u/611007 (20150623)
    >>> 
    >>> ## run this with `python -m doctest <this file>.py` from command line
    >>> 
    >>> txt = u"Tüskéshátú kígyóbűvölő"
    >>> txt2 = u"T\\u00fcsk\\u00e9sh\\u00e1t\\u00fa k\\u00edgy\\u00f3b\\u0171v\\u00f6l\\u0151"
    >>> txt3 = u"uúuutifu"
    >>> txt4 = b'u\\xfauutifu'
    >>> # txt4 shouldn't be 'u\\xc3\\xbauutifu', string content needs double backslash for doctest:
    >>> assert u'\\u0102' not in b'u\\xfauutifu'.decode('cp1250')
    >>> txt4u = txt4.decode('cp1250')
    >>> assert txt4u == u'u\\xfauutifu', repr(txt4u)
    >>> txt5 = b"u\\xc3\\xbauutifu"
    >>> txt5u = txt5.decode('utf-8')
    >>> txt6 = u"u\\u251c\\u2551uutifu"
    >>> there_and_back_again = lambda t: encode_items(t, encoding='utf-8').decode('utf-8')
    >>> assert txt == there_and_back_again(txt)
    >>> assert txt == there_and_back_again(txt2)
    >>> assert txt3 == there_and_back_again(txt3)
    >>> assert txt3.encode('cp852') == there_and_back_again(txt4u).encode('cp852')
    >>> assert txt3 == txt4u,(txt3,txt4u)
    >>> assert txt3 == there_and_back_again(txt5)
    >>> assert txt3 == there_and_back_again(txt5u)
    >>> assert txt3 == there_and_back_again(txt4u)
    >>> assert txt3.encode('cp1250') == encode_items(txt4, encoding='utf-8')
    >>> assert txt3.encode('utf-8') == encode_items(txt5, encoding='utf-8')
    >>> assert txt2.encode('utf-8') == encode_items(txt, encoding='utf-8')
    >>> assert {'a':txt2.encode('utf-8')} == encode_items({'a':txt}, encoding='utf-8')
    >>> assert [txt2.encode('utf-8')] == encode_items([txt], encoding='utf-8')
    >>> assert [[txt2.encode('utf-8')]] == encode_items([[txt]], encoding='utf-8')
    >>> assert [{'a':txt2.encode('utf-8')}] == encode_items([{'a':txt}], encoding='utf-8')
    >>> assert {'b':{'a':txt2.encode('utf-8')}} == encode_items({'b':{'a':txt}}, encoding='utf-8')
    """
    try:
        input.iteritems
        return {encode_items(k): encode_items(v) for (k,v) in input.iteritems()}
    except AttributeError:
        if isinstance(input, unicode):
            return input.encode(encoding)
        elif isinstance(input, str):
            return input
        try:
            iter(input)
            return [encode_items(e) for e in input]
        except TypeError:
            return input

def alt_dumps(obj, **kwargs):
    """
    >>> alt_dumps({'a': u"T\\u00fcsk\\u00e9sh\\u00e1t\\u00fa k\\u00edgy\\u00f3b\\u0171v\\u00f6l\\u0151"})
    '{"a": "T\\xc3\\xbcsk\\xc3\\xa9sh\\xc3\\xa1t\\xc3\\xba k\\xc3\\xadgy\\xc3\\xb3b\\xc5\\xb1v\\xc3\\xb6l\\xc5\\x91"}'
    """
    if 'ensure_ascii' in kwargs:
        del kwargs['ensure_ascii']
    return json.dumps(encode_items(obj), ensure_ascii=False, **kwargs)

私は思いも強調したいの答えジャレットハーディ参照JSONの仕様を引用:

文字列は0個以上のUnicode文字のコレクションです

私のユースケースでは、jsonのファイルがありました。それらはutf-8エンコードされたファイルです。ensure_ascii結果は適切にエスケープされますが、あまり読み込めないjsonファイルになります。そのため、Mark Ameryの回答を自分のニーズに合わせて調整しました。

doctestは特に思慮深いものではありませんが、誰かに役立つことを期待してコードを共有しています。


ここでダックタイピングを使用することの利点がわかりますか?から返されるコレクションはjson.loads、メソッドやマジックメソッドを実装するユーザー定義型やライブラリ定義型ではなく、リストまたはディクショナリになることがわかっているので、単にisinstanceチェックをしないのはなぜですか?オブジェクトの存在を確認するiteritemsiter、オブジェクトを引数として受け入れるかどうかを確認するよりも、理解しやすいでしょうか。
Mark Amery

@MarkAmeryこれはダンプではなく、ロードに関するものです。ロードするのではなく、ダンプするデータを作成する場合、それが何であるかはわかりません。アイデアは、コードのどこからでも取得できるようにすることでした。
n611x007 2015年

-2

このような同様の質問に対するこの回答を確認してください

u-接頭辞は、Unicode文字列があることを意味します。文字列を実際に使用すると、データに表示されません。印刷された出力によってスローされません。

たとえば、これを試してください:

print mail_accounts[0]["i"]

uは表示されません。


たとえば、Py2でUnicode文字列を含むものをフォーマットしたい場合は当てはまりません。たとえば、'{}'.format({u'x' : u'y'})まだuが含まれています。
Ponkadoodle
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.