web-dev-qa-db-ja.com

JSONからUnicodeの代わりに文字列オブジェクトを取得する方法

私はPython 2を使ってASCIIエンコードされたテキストファイルからJSONをパースします。

これらのファイルを json または simplejson のいずれかでロードすると、私の文字列値はすべて文字列オブジェクトではなくUnicodeオブジェクトにキャストされます。問題は、文字列オブジェクトだけを受け付けるライブラリでデータを使用しなければならないことです。 Iはライブラリの変更も更新もできません。

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を使うことである。

263
Brutus

ここでは良い答えがいくつかありますが、JSONファイルを解析するために PyYAML を使用しました。キーと値がstrname__型ではなくunicodename__型の文字列として渡されるためです。 JSONはYAMLのサブセットなので、うまく機能します。

>>> 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ファイルをロードするためにそれを使用する場合は、とにかくloadname__関数の「追加の機能」は必要ありません。

  • 仕様の1.2バージョンをもっとサポートするYAMLパーサが欲しいなら(そして 正しく少数の数値を解析する )試してみてください Ruamel YAMLpip install ruamel.yamlimport ruamel.yaml as yamlだけが必要でした私のテストで。

変換

述べたように、変換はありません! ASCIIの値だけを扱うのが確実でない場合(そしてほとんどの場合、確実に判断できない場合)は、変換関数を使用してください

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

175
Brutus

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以前をサポートするには、辞書の内包表記はPython 2.7までサポートされていなかったため、return {byteify(key): byteify(value) for key, value in input.iteritems()}return dict([(byteify(key), byteify(value)) for key, value in input.iteritems()])に置き換えます。
  • この答えはデコードされたオブジェクト全体を通して繰り返されるため、object_hookまたはobject_pairs_hookパラメータを慎重に使用することで回避できる、望ましくないパフォーマンス特性がいくつかあります。 Mirec Miskufの答え これは、これを正しく実現することができる唯一のものですが、結果として、それは私のアプローチよりもかなり複雑です。
139
Mark Amery

コンバーターを渡すには、 object_hookjson.loadsパラメーターを使用できます。あなたは事実の後に変換をする必要はありません。 json モジュールは常にobject_hook辞書のみを渡し、それは再帰的に入れ子辞書を渡しますので、自分で入れ子辞書に再帰する必要はありません。 Wellsが示すように、Unicode文字列を数値に変換するとは思わない。それがUnicode文字列である場合、それはJSONファイルで文字列として引用されていたので、それは文字列であることが想定されています(またはファイルが不良です)。

また、unicodeオブジェクトに対してstr(val)のようなことをしないようにします。あなたの外部ライブラリが何を期待しているかに応じて、あなたは有効なエンコーディングでvalue.encode(encoding)を使うべきです。

だから、例えば:

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)
73
Mike Brennan

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

私はJSONはUnicodeオブジェクトを返すのが正しいですだと思います。 JavaScriptの文字列実際にはunicodeのオブジェクトです(つまりJSON(javascript)の文字列はany kindを格納できます)そのため、JSONから文字列を変換するときにunicodeオブジェクトを作成することは意味があります。ライブラリはあなたが望むエンコーディングを推測しなければならないので、プレーンな文字列はちょうど収まりません。

どこでもunicodeという文字列オブジェクトを使うのが良いでしょう。ですから、あなたの最良の選択肢はあなたのライブラリがUnicodeオブジェクトを扱えるようにあなたのライブラリを更新することです。

しかし、もしあなたが本当にバイト文字列が欲しいのなら、結果をあなたの選んだエンコーディングにエンコードするだけです:

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

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

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文字列であるとき、これはより毛深いです。完全な答えはすぐに毛深いです。

14
Charles Merriam

Mike Brennan's answer は近いですが、構造全体を行き来する理由はありません。 object_hook_pairs (Python 2.7+)パラメータを使用すると、

object_pairs_hookは、ペアの順序付きリストでデコードされたオブジェクトリテラルの結果とともに呼び出されるオプションの関数です。 dictの代わりにobject_pairs_hookの戻り値が使用されます。この機能は、キーと値のペアがデコードされる順序に依存するカスタムデコーダを実装するために使用できます(たとえば、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つ少ないディクショナリーがインスタンス化されることになります。これは、巨大な文書を解析している場合には価値があります。

11
Travis Jensen

Simplejsonライブラリ内でこれを自動的に達成する方法がないのではないでしょうか。

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

しかしながら、simplejsonがUnicodeを出力するのは、 json spec が "文字列は0個以上のUnicode文字の集合"であると具体的に述べているためです... Unicodeのサポートはフォーマットの一部として想定されます自体。 Simplejsonのscanstringの実装は、Unicodeのエスケープをスキャンして解釈する(不正なマルチバイト文字セット表現のエラーチェックでさえも)ため、確実に値をUnicodeに返すことができる唯一の方法です。

strを必要とする古いライブラリを持っているのであれば、構文解析後にネストされたデータ構造を面倒に検索するか(明示的に避けたいと言っていることです...ごめんなさい)よりきめ細かいレベルで入力パラメータをマッサージできるファサードの。データ構造が実際に深くネストされている場合、2番目のアプローチは最初のアプローチよりも管理しやすいかもしれません。

9
Jarret Hardie

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

PyYamlアプローチについての2つの簡単なコメント:

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

  2. これによってcanを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)
    

しかし、パフォーマンスはMark Ameryの答えとは比較にならないほど賢明です。

深く入れ子になったサンプル辞書を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]

そのため、ツリーのandエンコーディングを完全にウォーキングすることを含む、直列化復元は、jsonのCベースの実装の桁の範囲内です。深くネストされた構造体では、これは驚くほど高速で、またその負荷も山積みの負荷よりも堅牢です。そしてyaml.loadを見て、セキュリティエラーが起こりにくくなりました。

=> Cベースのコンバーターへのポインターをいただければ幸いですが、byteify関数がデフォルトの答えになるはずです。

これは、json構造体がユーザー入力を含むフィールドからのものである場合に特に当てはまります。なぜならあなたはおそらくあなたの構造体の上をとにかく歩く必要があるからです - あなたの望む内部データ構造体( 'unicode sandwich'またはバイト文字列のみ)から独立しています。

どうして?

Unicode正規化知らない人のために:鎮痛剤を飲んで this と読んでください。

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

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

私のテストでは、input.encode( 'utf-8')をunicodedata.normalize( 'NFC'、input).encode( 'utf-8')に置き換えるほうがw/o NFC - しかし、それは私が推測するサンプルデータに大きく依存しています。

4
Red Pill

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

3
ducu

次のように、ダンプとロードには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'}
2

フックを使用してPython 2と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'}
1
abarik

だから、私は同じ問題に遭遇しました。最初のGoogleの結果はどうだったと思いますか。

私はすべてのデータをPyGTKに渡す必要があるので、Unicode文字列も私にとってあまり役に立ちません。だから私は別の再帰的な変換方法があります。型安全なJSON変換にも実際には必要です。json.dump()は、Pythonオブジェクトのようにリテラル以外のものを使用します。辞書インデックスは変換しません。

# 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
1
mario

私はjsonオブジェクト自身が配列であるケースを扱うためにWellsの_parse_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
darnmarshall

これはゲームに遅れていますが、私はこの再帰キャスターを作りました。それは私の必要性のために働きます、そして、私はそれが比較的完全であると思います。それはあなたを助けるかもしれません。

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)

私はクラスの非公開メンバーとしてそれを持っていますが、あなたが適切と思うようにあなたはメソッドを再利用することができます。

0
Wells

私は文字列としてJSON辞書を持っていました。キーと値は、次の例のようにUnicodeオブジェクトです。

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

ast.literal_eval(myStringDict)を使用して文字列をbyteifyオブジェクトに変換することで、上で提案したdict関数を使用できます。

0
narko

チェックアウト this これと似たような同様の質問への答え

接頭辞uは、Unicode文字列があることを意味します。あなたが本当に文字列を使うとき、それはあなたのデータに現れないでしょう。印刷された出力に投げ込まれないでください。

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

print mail_accounts[0]["i"]

あなたはuが見えないでしょう。

0
kunal

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

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

これは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
Red Pill