変換 - python json 独自 クラス




JSONをシリアライズ可能なクラスを作る方法 (15)

JSONダンプを独自のクラスにラップすることもできます。

import json

class FileItem:
    def __init__(self, fname):
        self.fname = fname

    def __repr__(self):
        return json.dumps(self.__dict__)

それとも、JsonSerializableクラスからFileItemクラスをサブクラス化する方が良いでしょう:

import json

class JsonSerializable(object):
    def toJson(self):
        return json.dumps(self.__dict__)

    def __repr__(self):
        return self.toJson()


class FileItem(JsonSerializable):
    def __init__(self, fname):
        self.fname = fname

テスト:

>>> f = FileItem('/foo/bar')
>>> f.toJson()
'{"fname": "/foo/bar"}'
>>> f
'{"fname": "/foo/bar"}'
>>> str(f) # string coercion
'{"fname": "/foo/bar"}'

どのようにPythonクラスをシリアライズ可能にするには?

簡単なクラス:

class FileItem:
    def __init__(self, fname):
        self.fname = fname

出力を得るにはどうすればよいですか:

json.dumps()

エラーなし( FileItem instance at ... is not JSON serializable


jsonwebが私にとって最高のソリューションだと思われます。 http://www.jsonweb.info/en/latest/参照してhttp://www.jsonweb.info/en/latest/

from jsonweb.encode import to_object, dumper

@to_object()
class DataModel(object):
  def __init__(self, id, value):
   self.id = id
   self.value = value

>>> data = DataModel(5, "foo")
>>> dumper(data)
'{"__type__": "DataModel", "id": 5, "value": "foo"}'

JSONFieldのモデルをPostgreSQL JSONFieldに保存しようとしたときにこの問題にJSONField

しばらくの間苦労した後、ここには一般的な解決策があります。

私の解決策の鍵は、Pythonのソースコードを見て、既存のjson.dumpsを他のデータ型をサポートするように拡張する方法について、コードのドキュメント( here説明していhere )がすでに説明していることです。

現在、JSONにシリアライズ不可能なフィールドがいくつか含まれているモデルがあり、JSONフィールドを含むモデルが元々このようになっているとします。

class SomeClass(Model):
    json_field = JSONField()

JSONEncoderようにカスタムJSONEncoder定義するだけです:

class CustomJsonEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, SomeTypeUnsupportedByJsonDumps):
            return < whatever value you want >
        return json.JSONEncoder.default(self, obj)

    @staticmethod
    def json_dumper(obj):
        return json.dumps(obj, cls=CustomJsonEncoder)

そして、 JSONField以下のように使用してください:

class SomeClass(Model):
    json_field = JSONField(dumps=CustomJsonEncoder.json_dumper)

キーは上記のdefault(self, obj)メソッドです。 あなたがPythonから受け取った... is not JSON serializable苦情で... is not JSON serializableなJSON型( Enumまたはdatetime )を処理するコードを追加するだけです。

たとえば、 Enumから継承したクラスをサポートする方法は次のとおりです。

class TransactionType(Enum):
   CURRENT = 1
   STACKED = 2

   def default(self, obj):
       if isinstance(obj, TransactionType):
           return obj.value
       return json.JSONEncoder.default(self, obj)

最後に、上記のように実装されたコードを使用して、Peeweeモデルを次のようなJSONのオブジェクトに変換することができます。

peewee_model = WhateverPeeweeModel()
new_model = SomeClass()
new_model.json_field = model_to_dict(peewee_model)

上記のコードはPeewee特有のものですが、私は思っています:

  1. 他のORM(Djangoなど)にも適用可能です
  2. また、 json.dumps仕組みを理解していれば、このソリューションも一般的にPython(ORM)には対応しています

ご質問は、コメント欄に投稿してください。 ありがとう!


jsonは印刷可能なオブジェクトの点で制限があり、 jsonpicklepip install jsonpickleが必要な場合があります)はテキストをインデントできないという点で制限があります。 クラスを変更できないオブジェクトの内容を検査したい場合、私はまだまっすぐな方法を見つけることができませんでした:

 import json
 import jsonpickle
 ...
 print  json.dumps(json.loads(jsonpickle.encode(object)), indent=2)

それでもオブジェクトメソッドは印刷できないことに注意してください。


あなたのクラスにto_jsonメソッドを次のように追加するto_jsonです:

def to_json(self):
  return self.message # or how you want it to be serialized

そして、このコードを( この回答から) 、すべての最上位のどこかに追加してください:

from json import JSONEncoder

def _default(self, obj):
    return getattr(obj.__class__, "to_json", _default.default)(obj)

_default.default = JSONEncoder().default
JSONEncoder.default = _default

これはJSONEncoder.default()が特別な "to_json()"メソッドを自動的にチェックし、見つかった場合にそのオブジェクトをエンコードするために使用されます。

Onurが言ったように、今度はプロジェクトのすべてのjson.dumps()を更新する必要はありません。


ここに私の3セントです...
これは木のようなpythonオブジェクトの明示的なjsonの直列化を示しています。
注:実際にこのようなコードが必要だった場合は、 Twisted FilePathクラスを使用できます。

import json, sys, os

class File:
    def __init__(self, path):
        self.path = path

    def isdir(self):
        return os.path.isdir(self.path)

    def isfile(self):
        return os.path.isfile(self.path)

    def children(self):        
        return [File(os.path.join(self.path, f)) 
                for f in os.listdir(self.path)]

    def getsize(self):        
        return os.path.getsize(self.path)

    def getModificationTime(self):
        return os.path.getmtime(self.path)

def _default(o):
    d = {}
    d['path'] = o.path
    d['isFile'] = o.isfile()
    d['isDir'] = o.isdir()
    d['mtime'] = int(o.getModificationTime())
    d['size'] = o.getsize() if o.isfile() else 0
    if o.isdir(): d['children'] = o.children()
    return d

folder = os.path.abspath('.')
json.dump(File(folder), sys.stdout, default=_default)

この問題には多くのアプローチがあります。 'ObjDict'(pip install objdict)は別のものです。 JSONからロードされたデータを処理するための辞書のように機能することができるオブジェクトのようなJavaScriptを提供することに重点が置かれていますが、他にも便利な機能があります。 これは元の問題に対する別の解決策を提供する。



もっと複雑なクラスの場合は、 jsonpickleというツールを使うことができます:

jsonpickleは、JSONとの間で複雑なPythonオブジェクトを直列化および逆シリアル化するためのPythonライブラリです。

stdlibのjson、simplejson、およびdemjsonなど、PythonをJSONにエンコードする標準のPythonライブラリは、直接のJSONに相当するPythonプリミティブ(たとえば、dicts、リスト、文字列、intなど)しか処理できません。 jsonpickleはこれらのライブラリの上に構築され、より複雑なデータ構造をJSONにシリアル化することができます。 jsonpickleは高度に構成可能で拡張可能であるため、ユーザーはJSONバックエンドを選択してバックエンドを追加できます。

(PyPiのjsonpickle)


シンプルな機能のためのシンプルなソリューションです:

.toJSON()メソッド

JSONシリアライズ可能クラスの代わりに、シリアライザメソッドを実装します。

import json

class Object:
    def toJSON(self):
        return json.dumps(self, default=lambda o: o.__dict__, 
            sort_keys=True, indent=4)

だからあなたはそれをシリアライズするために呼び出します:

me = Object()
me.name = "Onur"
me.age = 35
me.dog = Object()
me.dog.name = "Apollo"

print(me.toJSON())

出力されます:

{
    "age": 35,
    "dog": {
        "name": "Apollo"
    },
    "name": "Onur"
}

期待される成果について考えていますか? これはどういうことでしょうか?

>>> f  = FileItem("/foo/bar")
>>> magic(f)
'{"fname": "/foo/bar"}'

その場合、単にjson.dumps(f.__dict__)呼び出すことができます。

よりカスタマイズされた出力が必要な場合は、 JSONEncoderをサブクラスJSONEncoderし、独自のカスタムシリアル化を実装するJSONEncoderあります。

簡単な例については、以下を参照してください。

>>> from json import JSONEncoder
>>> class MyEncoder(JSONEncoder):
        def default(self, o):
            return o.__dict__    

>>> MyEncoder().encode(f)
'{"fname": "/foo/bar"}'

次に、このクラスをjson.dumps()メソッドにcls kwargとして渡します。

json.dumps(cls=MyEncoder)

デコードしたい場合は、カスタムobject_hookjson.dumps()クラスに提供する必要があります。 たとえば

>>> def from_json(json_object):
        if 'fname' in json_object:
            return FileItem(json_object['fname'])
>>> f = JSONDecoder(object_hook = from_json).decode('{"fname": "/foo/bar"}')
>>> f
<__main__.FileItem object at 0x9337fac>
>>> 

私はOnurの答えが気に入っていますが、オブジェクトを直列化するオプションのtoJSON()メソッドを含めるように拡張します:

def dumper(obj):
    try:
        return obj.toJSON()
    except:
        return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)

私は日時オブジェクトの直列化問題を解決するためにデコレータを使用することを選択しました。 ここに私のコードです:

#myjson.py
#Author: jmooremcc 7/16/2017

import json
from datetime import datetime, date, time, timedelta
"""
This module uses decorators to serialize date objects using json
The filename is myjson.py
In another module you simply add the following import statement:
    from myjson import json

json.dumps and json.dump will then correctly serialize datetime and date 
objects
"""

def json_serial(obj):
    """JSON serializer for objects not serializable by default json code"""

    if isinstance(obj, (datetime, date)):
        serial = str(obj)
        return serial
    raise TypeError ("Type %s not serializable" % type(obj))


def FixDumps(fn):
    def hook(obj):
        return fn(obj, default=json_serial)

    return hook

def FixDump(fn):
    def hook(obj, fp):
        return fn(obj,fp, default=json_serial)

    return hook


json.dumps=FixDumps(json.dumps)
json.dump=FixDump(json.dump)


if __name__=="__main__":
    today=datetime.now()
    data={'atime':today, 'greet':'Hello'}
    str=json.dumps(data)
    print str

上記のモジュールをインポートすることで、他のモジュールはjsonを通常の方法(デフォルトのキーワードを指定せずに)で使用して、日付オブジェクトを含むデータをシリアル化します。 日時シリアライザコードは、json.dumpsとjson.dumpに対して自動的に呼び出されます。


私は自分の解決策を思いついた。 このメソッドを使用して、任意のドキュメント( dictlistObjectIdなど)を渡してシリアル化します。

def getSerializable(doc):
    # check if it's a list
    if isinstance(doc, list):
        for i, val in enumerate(doc):
            doc[i] = getSerializable(doc[i])
        return doc

    # check if it's a dict
    if isinstance(doc, dict):
        for key in doc.keys():
            doc[key] = getSerializable(doc[key])
        return doc

    # Process ObjectId
    if isinstance(doc, ObjectId):
        doc = str(doc)
        return doc

    # Use any other custom serializting stuff here...

    # For the rest of stuff
    return doc

import simplejson

class User(object):
    def __init__(self, name, mail):
        self.name = name
        self.mail = mail

    def _asdict(self):
        return self.__dict__

print(simplejson.dumps(User('alice', '[email protected]')))

標準json使用する場合は、 default関数を定義する必要がありdefault

import json
def default(o):
    return o._asdict()

print(json.dumps(User('alice', '[email protected]'), default=default))




serialization