it-swarm.asia

كيفية JSON إجراء تسلسل مجموعات؟

لديّ 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 حسب المفتاح ، أم أنها مجرد تضمين/تجاهل عام ينظر إلى الكائن بالكامل؟ كيف تستوعب هذه الطريقة القيم المتداخلة؟ لقد بحثت في الأسئلة السابقة ولا يمكنني العثور على أفضل طريقة لترميز حالة معينة (يبدو للأسف أنه ما سأحتاج إلى فعله هنا).

120
DeaconDesperado

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 . كل هذه تدعم مجموعة أكبر بكثير من أنواع البيانات.

97
Raymond Hettinger

يمكنك إنشاء برنامج تشفير مخصص يُرجع 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"]'
88
jterrace

تتوفر فقط القواميس والقوائم وأنواع الكائنات البدائية (int ، string ، bool) في JSON.

5
Joseph Le Brech

لقد تكيفت حل ريمون هيتنجر لثعبان 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')]
4
simlmx

إذا كنت بحاجة فقط إلى تشفير المجموعات ، وليس كائنات 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
3
NeilenMarais