web-dev-qa-db-ja.com

クラスJSONをシリアライズ可能にする方法

Pythonクラスを直列化可能にする方法

簡単なクラス

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

私が出力を得ることができるようにするにはどうすればいいですか:

json.dumps()

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

626
Sergey

予想される出力についての考えはありますか?例えばこれはしますか?

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

その場合、あなたは単にjson.dumps(f.__dict__)を呼ぶことができます。

もっとカスタマイズされた出力が欲しいなら、 JSONEncoder をサブクラス化してあなた独自のカスタムシリアル化を実装する必要があります。

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

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

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

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

json.dumps(cls=MyEncoder)

デコードもしたい場合は、カスタムのobject_hookJSONDecoder クラスに指定する必要があります。例えば.

>>> 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>
>>> 
460
Manoj Govindan

簡単な機能に対する簡単な解決策は次のとおりです。

.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"
}
513

より複雑なクラスの場合は、 jsonpickle というツールを検討できます。

jsonpickleは、JSONとの間での複雑なPythonオブジェクトのシリアライゼーションおよびデシリアライゼーションのためのPythonライブラリです。

Stdlibのjson、simplejson、demjsonなど、PythonをJSONにエンコードするための標準Pythonライブラリは、直接のJSONと同等のものを持つPythonプリミティブ(辞書、リスト、文字列、整数など)しか処理できません。 jsonpickleはこれらのライブラリの上に構築されており、より複雑なデータ構造をJSONにシリアル化することができます。 jsonpickleは高度に設定可能で拡張可能です - ユーザはJSONバックエンドを選択して追加のバックエンドを追加することができます。

(PyPiのjsonpickleへのリンク)

140
gecco

答えのほとんどは、 json.dumps() への呼び出しを変更することを含みます。

json.dumps(obj) をそのまま呼び出すことができるようにするには、単純な解決策として dict を継承します。

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

f = FileItem('tasks.txt')
json.dumps(f)  #No need to change anything here

これはあなたのクラスが単なる基本的なデータ表現である場合にはうまくいきます。

58
andyhasit

もう1つの選択肢は、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"}'
37
Paulo Freitas

私は Onurの答え を好みますが、オブジェクトを自分自身で直列化するためのオプションのtoJSON()メソッドを含めるように拡張します。

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

私は先日この問題に遭遇し、入れ子になったオブジェクトを扱う継承されたフィールドを持つことができるPythonオブジェクト用のより一般的なバージョンのEncoderを実装しました。

import json
import inspect

class ObjectEncoder(json.JSONEncoder):
    def default(self, obj):
        if hasattr(obj, "to_json"):
            return self.default(obj.to_json())
        Elif hasattr(obj, "__dict__"):
            d = dict(
                (key, value)
                for key, value in inspect.getmembers(obj)
                if not key.startswith("__")
                and not inspect.isabstract(value)
                and not inspect.isbuiltin(value)
                and not inspect.isfunction(value)
                and not inspect.isgenerator(value)
                and not inspect.isgeneratorfunction(value)
                and not inspect.ismethod(value)
                and not inspect.ismethoddescriptor(value)
                and not inspect.isroutine(value)
            )
            return self.default(d)
        return obj

例:

class C(object):
    c = "NO"
    def to_json(self):
        return {"c": "YES"}

class B(object):
    b = "B"
    i = "I"
    def __init__(self, y):
        self.y = y

    def f(self):
        print "f"

class A(B):
    a = "A"
    def __init__(self):
        self.b = [{"ab": B("y")}]
        self.c = C()

print json.dumps(A(), cls=ObjectEncoder, indent=2, sort_keys=True)

結果:

{
  "a": "A", 
  "b": [
    {
      "ab": {
        "b": "B", 
        "i": "I", 
        "y": "y"
      }
    }
  ], 
  "c": {
    "c": "YES"
  }, 
  "i": "I"
}
24
tobigue

次のようにクラスにto_jsonメソッドを追加するだけです。

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

そして、このコード (from この答えを、一番上のどこかに追加してください。

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()はインポート時にjsonモジュールにモンキーパッチを適用するので、特別な "to_json()"メソッドを自動的にチェックし、見つかった場合はそれを使用してオブジェクトをエンコードします。

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

20
Fancy John
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関数を定義する必要があります。

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

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

Python 3.5以降を使用している場合は、 jsons を使用できます。それはあなたのオブジェクト(そして再帰的に全ての属性)を辞書に変換します。

import jsons

a_dict = jsons.dump(your_object)

あるいは、文字列が欲しいなら:

a_str = jsons.dumps(your_object)

あるいはあなたのクラスがjsons.JsonSerializableを実装しているなら:

a_dict = your_object.json
6
R H

このクラスはトリックをすることができます、それはオブジェクトを標準のjsonに変換します。

import json


class Serializer(object):
    @staticmethod
    def serialize(object):
        return json.dumps(object, default=lambda o: o.__dict__.values()[0])

使用法:

Serializer.serialize(my_object)

python2.7python3で働いています。

5
Lost Koder

jsonは印刷できるオブジェクトの点で制限されており、jsonpicklepip install jsonpickleが必要な場合があります)はテキストをインデントできないという点で制限されています。クラスを変更できないobjecthの内容を調べたいのであれば、私はまだ次のような単純な方法を見つけることができませんでした。

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

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

5
ribamar
import json

class Foo(object):
    def __init__(self):
        self.bar = 'baz'
        self._qux = 'flub'

    def somemethod(self):
        pass

def default(instance):
    return {k: v
            for k, v in vars(instance).items()
            if not str(k).startswith('_')}

json_foo = json.dumps(Foo(), default=default)
assert '{"bar": "baz"}' == json_foo

print(json_foo)
4
rectangletangle

jaraco はかなりきちんとした答えを出した。細かいことをいくつか修正する必要がありましたが、これでうまくいきます。

コード

# Your custom class
class MyCustom(object):
    def __json__(self):
        return {
            'a': self.a,
            'b': self.b,
            '__python__': 'mymodule.submodule:MyCustom.from_json',
        }

    to_json = __json__  # supported by simplejson

    @classmethod
    def from_json(cls, json):
        obj = cls()
        obj.a = json['a']
        obj.b = json['b']
        return obj

# Dumping and loading
import simplejson

obj = MyCustom()
obj.a = 3
obj.b = 4

json = simplejson.dumps(obj, for_json=True)

# Two-step loading
obj2_dict = simplejson.loads(json)
obj2 = MyCustom.from_json(obj2_dict)

# Make sure we have the correct thing
assert isinstance(obj2, MyCustom)
assert obj2.__dict__ == obj.__dict__

ロードには2つのステップが必要です。今のところ、__python__プロパティは使用されていません。

これはどのくらい一般的ですか。

AlJohri の方法を使って、私はアプローチの人気をチェックする:

直列化(Python - > JSON):

逆シリアル化(JSON - > Python):

4
Martin Thoma

パッケージをインストールしても構わない場合は、 json-tricks を使用できます。

pip install json-tricks

その後は、jsonの代わりにjson_tricksからdump(s)をインポートするだけでよく、通常はうまくいきます。

from json_tricks import dumps
json_str = dumps(cls_instance, indent=4)

それはあげる

{
        "__instance_type__": [
                "module_name.test_class",
                "MyTestCls"
        ],
        "attributes": {
                "attr": "val",
                "dct_attr": {
                        "hello": 42
                }
        }
}

そしてそれは基本的にそれです!


これは概してうまくいきます。いくつかの例外があります。 __new__で特別なことが起こった場合、またはもっとメタクラスの魔法が起こっている場合.

明らかにロードもうまくいきます(そうでなければ何がポイントです):

from json_tricks import loads
json_str = loads(json_str)

これはmodule_name.test_class.MyTestClsがインポート可能で互換性のない方法で変更されていないことを前提としています。 あなたはインスタンスを取り戻すでしょう 、辞書や何かではなく、そしてそれはあなたが捨てたものと同一のコピーであるべきです。

あなたが何かが(非)シリアライズされる方法をカスタマイズしたいのなら、あなたはあなたのクラスに特別なメソッドを追加することができます。

class CustomEncodeCls:
        def __init__(self):
                self.relevant = 42
                self.irrelevant = 37

        def __json_encode__(self):
                # should return primitive, serializable types like dict, list, int, string, float...
                return {'relevant': self.relevant}

        def __json_decode__(self, **attrs):
                # should initialize all properties; note that __init__ is not called implicitly
                self.relevant = attrs['relevant']
                self.irrelevant = 12

例として、attributesパラメータの一部だけをシリアル化します。

そして無料のボーナスとして、あなたは(数えきれないほどの)配列、日付と時刻、順序付けされた地図、そしてjsonにコメントを含める能力のシリアル化を得ます。

免責事項:私はあなたと同じ問題を抱えていたので、私は json_tricks を作成しました。

2
Mark

jsonwebは私にとって最良の解決策のようです。 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"}'
2
matthewlent

これは私にはうまくいきました:

class JsonSerializable(object):

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

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

    @staticmethod
    def dumper(obj):
        if "serialize" in dir(obj):
            return obj.serialize()

        return obj.__dict__

その後

class FileItem(JsonSerializable):
    ...

そして

log.debug(json.dumps(<my object>, default=JsonSerializable.dumper, indent=2))
2
jmhostalet

PeeweeのモデルをPostgreSQLのJSONFieldに保存しようとしたときに、この問題に遭遇しました。

しばらく苦労した後、これが一般的な解決策です。

私の解決策への鍵は、Pythonのソースコードを通して、コードド​​キュメンテーション( here で説明されています)が他のデータ型をサポートするために既存のjson.dumpsを拡張する方法をすでに説明していることを理解することです。

現在、JSONに直列化できないいくつかのフィールドを含むモデルがあり、JSONフィールドを含むモデルは元々次のようになっているとします。

class SomeClass(Model):
    json_field = JSONField()

このようにカスタムの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苦情に対して、シリアライズ不可能なJSON型を処理するコードを追加するだけです(Enumdatetimeなど)。

たとえば、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(sans ORM)一般にも機能します。

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

1
sivabudh

これが私の3セントです...
これはツリーのようなpythonオブジェクトに対する明示的なjsonシリアライゼーションを示しています。
注:実際にこのようなコードが必要な場合は、 ひねった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)
1
Dan Brough

あなたがパッケージをインストールすることができるならば、私は dill を試みることを勧めます。このパッケージのいいところは、pickleと同じインターフェースを持っているということです。そのため、プロジェクトで既にpickleを使用している場合は、コードを変更せずに単にdillに置き換えてスクリプトを実行するかどうかを確認できます。だから試してみるのはとても安い解決策です!

(完全な非開示:私は決してディルプロジェクトと提携していないし、ディルプロジェクトに貢献したこともありません。)

パッケージをインストールしてください。

pip install dill

次に、コードを編集してdillの代わりにpickleをインポートします。

# import pickle
import dill as pickle

スクリプトを実行して、それが機能するかどうかを確認してください。 (もしそうなら、あなたはもはやpickleモジュール名をシャドウイングしないようにあなたのコードをクリーンアップしたいかもしれません!)

プロジェクトページ から、dillがシリアライズすることができる、またはできないデータ型に関するいくつかの詳細

dillは以下の標準型を選択することができます。

none、type、bool、int、long、float、複素数、str、Unicode、タプル、リスト、辞書、ファイル、バッファ、組み込み、新旧両方のスタイルクラス、セット、固定セット、配列、機能、例外

dillはより多くの「エキゾチックな」標準型を漬けることもできます。

yield付き関数、入れ子関数、ラムダ、セル、メソッド、非バインド方法、モジュール、コード、methodwrapper、dictproxy、methoddescriptor、getsetdescriptor、memberdescriptor、wrapperdescriptor、xrange、スライス、未実装、省略記号、終了

dillはまだこれらの標準的な型をpickleすることができません:

フレーム、ジェネレータ、トレースバック

0
thedavidmo

私は自分の解決策を思いついた。このメソッドを使用し、シリアル化するために任意のドキュメント( dict list ObjectId etc)を渡します。

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

これは、すべての子を持つオブジェクトをJSONにシリアル化し、それを解析して戻す小さなライブラリです。

https://github.com/Toubs/PyJSONSerialization/ /

0
Tobi

ここではシリアルバージョン管理やバックコンパチについて言及していないので、少しの間使用していたソリューションを投稿します。私はおそらくより多くのことを学ぶ必要があります。具体的にはJavaとJavascriptはおそらく私よりも成熟していますが、ここでは

https://Gist.github.com/andy-d/b7878d0044a4242c0498ed6d67fd50fe

0
Fletch F Fletch

Lost Koderの方法が一番好きでした。メンバー/メソッドが直列化できないより複雑なオブジェクトを直列化しようとしたとき、私は問題にぶつかりました。これが私の実装です。

class Serializer(object):
    @staticmethod
    def serialize(obj):
        def check(o):
            for k, v in o.__dict__.items():
                try:
                    _ = json.dumps(v)
                    o.__dict__[k] = v
                except TypeError:
                    o.__dict__[k] = str(v)
            return o
        return json.dumps(check(obj).__dict__, indent=2)
0
Will Charlton

Datetimeオブジェクトのシリアル化問題を解決するためにデコレータを使うことにしました。これが私のコードです:

#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

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

0
John Moore