لديّ Python set
الذي يحتوي على كائنات لها طرق __hash__
و __eq__
من أجل التأكد من عدم إدراج أي مكررات في المجموعة.
أحتاج إلى json لتشفير هذه النتيجة set
، لكن تمرير حتى set
فارغ إلى طريقة json.dumps
يثير TypeError
.
File "/usr/lib/python2.7/json/encoder.py", line 201, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib/python2.7/json/encoder.py", line 264, in iterencode
return _iterencode(o, 0)
File "/usr/lib/python2.7/json/encoder.py", line 178, in default
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: set([]) is not JSON serializable
أعلم أنه يمكنني إنشاء امتداد لفئة json.JSONEncoder
التي تحتوي على طريقة default
مخصصة ، لكنني لست متأكدًا من أين أبدأ في التحويل عبر set
. هل يجب أن أقوم بإنشاء قاموس من قيم set
ضمن الطريقة الافتراضية ، ثم أعد الترميز على ذلك؟ من الناحية المثالية ، أرغب في جعل الطريقة الافتراضية قادرة على التعامل مع جميع أنواع البيانات التي يختفي بها المشفر الأصلي (أنا أستخدم Mongo كمصدر بيانات لذلك يبدو أن التواريخ تثير هذا الخطأ أيضًا)
سيكون موضع تقدير أي تلميح في الاتجاه الصحيح.
تصحيح:
شكرا على الاجابة! ربما كان ينبغي أن أكون أكثر دقة.
لقد استخدمت (وحصلت على أصوات) الإجابات هنا للتغلب على قيود set
التي تتم ترجمتها ، ولكن هناك مفاتيح داخلية تشكل مشكلة أيضًا.
الكائنات الموجودة في set
هي كائنات معقدة تترجم إلى __dict__
، لكنها يمكن أن تحتوي أيضًا على قيم لخصائصها التي قد تكون غير مؤهلة للأنواع الأساسية في برنامج ترميز json.
هناك الكثير من الأنواع المختلفة التي تدخل في هذا set
، والتجزئة تحسب بشكل أساسي معرفًا فريدًا للكيان ، ولكن وفقًا للروح الحقيقية لـ NoSQL لا يوجد ما يحدد بالضبط ما يحتوي عليه الكائن الفرعي.
قد يحتوي أحد الكائنات على قيمة تاريخ لـ starts
، بينما قد يحتوي كائن آخر على بعض المخططات الأخرى التي لا تتضمن مفاتيح تحتوي على كائنات "غير بدائية".
هذا هو السبب في أن الحل الوحيد الذي يمكن أن أفكر فيه هو تمديد JSONEncoder
لاستبدال طريقة default
لتشغيل الحالات المختلفة - لكنني لست متأكدًا من كيفية متابعة ذلك والوثائق غامضة. في الكائنات المتداخلة ، هل تذهب القيمة التي يتم إرجاعها من default
حسب المفتاح ، أم أنها مجرد تضمين/تجاهل عام ينظر إلى الكائن بالكامل؟ كيف تستوعب هذه الطريقة القيم المتداخلة؟ لقد بحثت في الأسئلة السابقة ولا يمكنني العثور على أفضل طريقة لترميز حالة معينة (يبدو للأسف أنه ما سأحتاج إلى فعله هنا).
JSON تدوين يحتوي على حفنة فقط من أنواع البيانات الأصلية (كائنات ، صفائف ، سلاسل ، أرقام ، منطقية ، و خالية) ، لذلك يجب التعبير عن أي شيء متسلسل في JSON كواحد من هذه الأنواع.
كما هو موضح في مستندات الوحدة النمطية json ، يمكن إجراء هذا التحويل تلقائيًا بواسطة JSONEncoder و JSONDecoder ، ولكن بعد ذلك ستتخلى عن بنية أخرى قد تحتاجها ( إذا قمت بتحويل مجموعات إلى قائمة ، فستفقد القدرة على استرداد القوائم العادية ؛ وإذا قمت بتحويل مجموعات إلى قاموس باستخدام dict.fromkeys(s)
فإنك تفقد القدرة على استرداد القواميس).
الحل الأكثر تعقيدًا هو إنشاء نوع مخصص يمكن أن يتعايش مع أنواع JSON الأصلية الأخرى. يتيح لك ذلك تخزين الهياكل المتداخلة التي تتضمن قوائم ، مجموعات ، رسومات ، عشري ، كائنات وقت ، إلخ:
from json import dumps, loads, JSONEncoder, JSONDecoder
import pickle
class PythonObjectEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, (list, dict, str, unicode, int, float, bool, type(None))):
return JSONEncoder.default(self, obj)
return {'_python_object': pickle.dumps(obj)}
def as_python_object(dct):
if '_python_object' in dct:
return pickle.loads(str(dct['_python_object']))
return dct
فيما يلي نموذج لجلسة توضح أنه قادر على التعامل مع القوائم والنصوص والمجموعات:
>>> data = [1,2,3, set(['knights', 'who', 'say', 'ni']), {'key':'value'}, Decimal('3.14')]
>>> j = dumps(data, cls=PythonObjectEncoder)
>>> loads(j, object_hook=as_python_object)
[1, 2, 3, set(['knights', 'say', 'who', 'ni']), {u'key': u'value'}, Decimal('3.14')]
بدلاً من ذلك ، قد يكون من المفيد استخدام تقنية التسلسل للأغراض العامة مثل YAML / Twisted Jelly أو أو Python's pickle module . كل هذه تدعم مجموعة أكبر بكثير من أنواع البيانات.
يمكنك إنشاء برنامج تشفير مخصص يُرجع list
عندما يواجه set
. إليك مثال:
>>> import json
>>> class SetEncoder(json.JSONEncoder):
... def default(self, obj):
... if isinstance(obj, set):
... return list(obj)
... return json.JSONEncoder.default(self, obj)
...
>>> json.dumps(set([1,2,3,4,5]), cls=SetEncoder)
'[1, 2, 3, 4, 5]'
يمكنك اكتشاف أنواع أخرى بهذه الطريقة أيضًا. إذا كنت بحاجة إلى الاحتفاظ بأن القائمة كانت بالفعل مجموعة ، فيمكنك استخدام ترميز مخصص. شيء مثل return {'type':'set', 'list':list(obj)}
قد يعمل.
لتوضيح الأنواع المتداخلة ، ضع في الاعتبار تسلسل هذا:
>>> class Something(object):
... pass
>>> json.dumps(set([1,2,3,4,5,Something()]), cls=SetEncoder)
هذا يثير الخطأ التالي:
TypeError: <__main__.Something object at 0x1691c50> is not JSON serializable
يشير هذا إلى أن المشفر سيأخذ النتيجة list
التي يتم إرجاعها ويتصل متكرر بالمتسلسل على أطفاله. لإضافة مُسلسل مخصص لأنواع متعددة ، يمكنك القيام بذلك:
>>> class SetEncoder(json.JSONEncoder):
... def default(self, obj):
... if isinstance(obj, set):
... return list(obj)
... if isinstance(obj, Something):
... return 'CustomSomethingRepresentation'
... return json.JSONEncoder.default(self, obj)
...
>>> json.dumps(set([1,2,3,4,5,Something()]), cls=SetEncoder)
'[1, 2, 3, 4, 5, "CustomSomethingRepresentation"]'
تتوفر فقط القواميس والقوائم وأنواع الكائنات البدائية (int ، string ، bool) في JSON.
لقد تكيفت حل ريمون هيتنجر لثعبان 3.
هنا ما الذي تغير:
unicode
اختفىdefault
مع super()
base64
لتسلسل نوع bytes
إلى str
(لأنه يبدو أنه لا يمكن تحويل bytes
في python 3 إلى JSON)from decimal import Decimal
from base64 import b64encode, b64decode
from json import dumps, loads, JSONEncoder
import pickle
class PythonObjectEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, (list, dict, str, int, float, bool, type(None))):
return super().default(obj)
return {'_python_object': b64encode(pickle.dumps(obj)).decode('utf-8')}
def as_python_object(dct):
if '_python_object' in dct:
return pickle.loads(b64decode(dct['_python_object'].encode('utf-8')))
return dct
data = [1,2,3, set(['knights', 'who', 'say', 'ni']), {'key':'value'}, Decimal('3.14')]
j = dumps(data, cls=PythonObjectEncoder)
print(loads(j, object_hook=as_python_object))
# prints: [1, 2, 3, {'knights', 'who', 'say', 'ni'}, {'key': 'value'}, Decimal('3.14')]
إذا كنت بحاجة فقط إلى تشفير المجموعات ، وليس كائنات Python العامة ، وترغب في إبقائها سهلة القراءة ، فيمكن استخدام إصدار مبسط من إجابة Raymond Hettinger:
import json
import collections
class JSONSetEncoder(json.JSONEncoder):
"""Use with json.dumps to allow Python sets to be encoded to JSON
Example
-------
import json
data = dict(aset=set([1,2,3]))
encoded = json.dumps(data, cls=JSONSetEncoder)
decoded = json.loads(encoded, object_hook=json_as_python_set)
assert data == decoded # Should assert successfully
Any object that is matched by isinstance(obj, collections.Set) will
be encoded, but the decoded value will always be a normal Python set.
"""
def default(self, obj):
if isinstance(obj, collections.Set):
return dict(_set_object=list(obj))
else:
return json.JSONEncoder.default(self, obj)
def json_as_python_set(dct):
"""Decode json {'_set_object': [1,2,3]} to set([1,2,3])
Example
-------
decoded = json.loads(encoded, object_hook=json_as_python_set)
Also see :class:`JSONSetEncoder`
"""
if '_set_object' in dct:
return set(dct['_set_object'])
return dct