用于复杂numpy数组的Json编码器和解码器

时间:2015-01-12 19:50:50

标签: python json serialization numpy encoding

我正在尝试JSON编码一个复杂的numpy数组,我为此找到了一个来自astropy(http://astropy.readthedocs.org/en/latest/_modules/astropy/utils/misc.html#JsonCustomEncoder)的实用程序:

import numpy as np

class JsonCustomEncoder(json.JSONEncoder):
    """ <cropped for brevity> """
    def default(self, obj):
        if isinstance(obj, (np.ndarray, np.number)):
            return obj.tolist()
        elif isinstance(obj, (complex, np.complex)):
            return [obj.real, obj.imag]
        elif isinstance(obj, set):
            return list(obj)
        elif isinstance(obj, bytes):  # pragma: py3
            return obj.decode()
        return json.JSONEncoder.default(self, obj)

这适用于复杂的numpy数组:

test = {'some_key':np.array([1+1j,2+5j, 3-4j])}

倾销收益率:

encoded = json.dumps(test, cls=JsonCustomEncoder)
print encoded
>>> {"some key": [[1.0, 1.0], [2.0, 5.0], [3.0, -4.0]]}

问题是,我无法自动将其读回复杂数组。例如:

json.loads(encoded)
>>> {"some_key": [[1.0, 1.0], [2.0, 5.0], [3.0, -4.0]]}

你能帮助我找出覆盖加载/解码的方法,以便推断它必须是一个复杂的数组吗? I.E.它应该只是将它们放回一个复杂的数组中,而不是一个2元素项的列表。 JsonCustomDecoder没有覆盖default()方法,编码文档对我来说有太多术语。

4 个答案:

答案 0 :(得分:4)

这是我的最终解决方案,改编自hpaulj的回答,以及他对这个帖子的回答:https://stackoverflow.com/a/24375113/901925

这将编码/解码嵌套在任何数据类型的嵌套字典中任意深度的数组。

import base64
import json
import numpy as np

class NumpyEncoder(json.JSONEncoder):
    def default(self, obj):
        """
        if input object is a ndarray it will be converted into a dict holding dtype, shape and the data base64 encoded
        """
        if isinstance(obj, np.ndarray):
            data_b64 = base64.b64encode(obj.data)
            return dict(__ndarray__=data_b64,
                        dtype=str(obj.dtype),
                        shape=obj.shape)
        # Let the base class default method raise the TypeError
        return json.JSONEncoder(self, obj)


def json_numpy_obj_hook(dct):
    """
    Decodes a previously encoded numpy ndarray
    with proper shape and dtype
    :param dct: (dict) json encoded ndarray
    :return: (ndarray) if input was an encoded ndarray
    """
    if isinstance(dct, dict) and '__ndarray__' in dct:
        data = base64.b64decode(dct['__ndarray__'])
        return np.frombuffer(data, dct['dtype']).reshape(dct['shape'])
    return dct

# Overload dump/load to default use this behavior.
def dumps(*args, **kwargs):
    kwargs.setdefault('cls', NumpyEncoder)
    return json.dumps(*args, **kwargs)

def loads(*args, **kwargs):
    kwargs.setdefault('object_hook', json_numpy_obj_hook)    
    return json.loads(*args, **kwargs)

def dump(*args, **kwargs):
    kwargs.setdefault('cls', NumpyEncoder)
    return json.dump(*args, **kwargs)

def load(*args, **kwargs):
    kwargs.setdefault('object_hook', json_numpy_obj_hook)
    return json.load(*args, **kwargs)

if __name__ == '__main__':

    data = np.arange(3, dtype=np.complex)

    one_level = {'level1': data, 'foo':'bar'}
    two_level = {'level2': one_level}

    dumped = dumps(two_level)
    result = loads(dumped)

    print '\noriginal data', data
    print '\nnested dict of dict complex array', two_level
    print '\ndecoded nested data', result

产生输出:

original data [ 0.+0.j  1.+0.j  2.+0.j]

nested dict of dict complex array {'level2': {'level1': array([ 0.+0.j,  1.+0.j,  2.+0.j]), 'foo': 'bar'}}

decoded nested data {u'level2': {u'level1': array([ 0.+0.j,  1.+0.j,  2.+0.j]), u'foo': u'bar'}}

答案 1 :(得分:4)

accepted answer很棒,但有一个缺陷。它仅在您的数据为C_CONTIGUOUS时有效。如果您转置数据,则情况并非如此。例如,测试以下内容:

$( "#draggable" ).draggable({
    revert:true, 
    refreshPositions: true
});

要解决此问题,请在将对象传递给b64encode时使用'np.ascontiguousarray()'。具体来说,改变:

A = np.arange(10).reshape(2,5)
A.flags
# C_CONTIGUOUS : True
# F_CONTIGUOUS : False
# OWNDATA : False
# WRITEABLE : True
# ALIGNED : True
# UPDATEIFCOPY : False
A = A.transpose()
#array([[0, 5],
#       [1, 6],
#       [2, 7],
#       [3, 8],
#       [4, 9]])
loads(dumps(A))
#array([[0, 1],
#       [2, 3],
#       [4, 5],
#       [6, 7],
#       [8, 9]])
A.flags
# C_CONTIGUOUS : False
# F_CONTIGUOUS : True
# OWNDATA : False
# WRITEABLE : True
# ALIGNED : True
# UPDATEIFCOPY : False

TO:

data_b64 = base64.b64encode(obj.data)

如果我正确理解了这个功能,那么如果你的数据已经是C_CONTIGUOUS则不会采取任何行动,因此唯一的表现就是当你有F_CONTIGUOUS数据时。

答案 2 :(得分:1)

目前还不清楚json编码/解码或使用numpy需要多少帮助。例如,你是如何创建复杂数组的呢?

您的编码所做的是将数组渲染为列表列表。解码器不得不将其转换回适当的dtype数组。例如:

d = json.loads(encoded)
a = np.dot(d['some_key'],np.array([1,1j]))
# array([ 1.+1.j,  2.+5.j,  3.-4.j])

这不是从这个列表创建这样一个数组的唯一方法,它可能会失败,形式更一般,但它是一个开始。

下一个任务是确定何时使用这样的例行程序。如果您知道要接收这样的数组,那么就进行解码。

另一种选择是将一个或多个键添加到字典中,将该变量标记为复杂的nparray。一个键也可以对其形状进行编码(尽管这也可以从列表列表的嵌套中进行推导)。

这是否指向了正确的方向?或者您是否需要每一步的进一步帮助?


这个'SimpleJSON和NumPy数组'问题的答案之一

https://stackoverflow.com/a/24375113/901925

处理numpy数组的编码和解码。它使用dtype和shape以及数组的数据缓冲区对字典进行编码。所以JSON字符串对人类来说意义不大。但是会处理一般数组,包括具有复杂dtype的数组。

expecteddump打印件为:

[ 1.+1.j  2.+5.j  3.-4.j]

{"dtype": "complex128", "shape": [3], 
    "__ndarray__": "AAAAAAAA8D8AAAAAAADwPwAAAAAAAABAAAAAAAAAFEAAAAAAAAAIQAAAAAAAABDA"}

自定义解码使用object_hook函数完成,该函数接受dict并返回一个数组(如果可能)。

json.loads(dumped, object_hook=json_numpy_obj_hook)

遵循该模型,这里有一个粗略的hook,它将每个JSON数组转换为np.array,每个有2列的数据转换为1d复杂数组:

def numpy_hook(dct):
    jj = np.array([1,1j])
    for k,v in dct.items():
        if isinstance(v, list):
            v = np.array(v)
            if v.ndim==2 and v.shape[1]==2:
                v = np.dot(v,jj)
            dct[k] = v
    return dct

我认为,将一些字典键编码为标记numpy array,将另一个字典键标记为complex dtype会更好。


我可以改进钩子来处理常规列表和其他数组维度:

def numpy_hook(dct):
    jj = np.array([1,1j])
    for k,v in dct.items():
        if isinstance(v, list):
            # try to turn list into numpy array
            v = np.array(v)
            if v.dtype==object:
                # not a normal array, don't change it
                continue
            if v.ndim>1 and v.shape[-1]==2:
                # guess it is a complex array
                # this information should be more explicit
                v = np.dot(v,jj)
            dct[k] = v
    return dct

它处理这个结构:

A = np.array([1+1j,2+5j, 3-4j])
B = np.arange(12).reshape(3,4)
C = A+B.T
test = {'id': 'stream id',
        'arrays': [{'A': A}, {'B': B}, {'C': C}]}

返回:

{u'arrays': [{u'A': array([ 1.+1.j,  2.+5.j,  3.-4.j])}, 
       {u'B': array([[ 0,  1,  2,  3],
                     [ 4,  5,  6,  7],
                     [ 8,  9, 10, 11]])}, 
       {u'C': array([[  1.+1.j,   6.+5.j,  11.-4.j],
                     [  2.+1.j,   7.+5.j,  12.-4.j],
                     [  3.+1.j,   8.+5.j,  13.-4.j],
                     [  4.+1.j,   9.+5.j,  14.-4.j]])}], 
 u'id': u'stream id'}

我认为,任何更多的一般性都需要修改编码以使数组标识显式化。

答案 3 :(得分:0)

尝试traitschema https://traitschema.readthedocs.io/en/latest/

  

“使用traits和Numpy创建可序列化,类型检查的模式。典型的用例包括保存几个不同形状和类型的Numpy数组。”

参见 to_json()

  

“这使用自定义JSON编码器处理numpy数组,但可能会失去精度。如果这很重要,请考虑使用HDF5格式进行序列化”