在通过bson进行数据传输时经常会遇到BSON无法转化自定义类型的异常。从其代码中没有发现提供转换接口,一般只能在外界先将数据转化之后,再交由bson进行编码。

很多时候这种转化代码并不方便,看着也不简洁。因此考虑直接在BSON库中增加自定义类型的转化处理。

修改bson/__init__.py

bson主体逻辑都在bson/__init__.py内,从其代码分析,如果能动态增加bson._ENCODERSbson._ELEMENT_GETTER中的匹配关系就能够实现自定义类型的转化处理。

def update_bson(identifier, klass, encoder_func, decoder_dunc):
	bson._ENCODERS.update({
		klass: encoder_func,
	})
	bson._ELEMENT_GETTER.update({
		identifier: decoder_dunc,
	})

def encoder_func(name, value, dummy0, dummy1):
	pass

def decoder_func(data, position, dummy0, dummy1, dummy2):
	pass

bson数据进行编码时其格式为,

ID + NAME + DATA1 + DATA2 + ...

解析的时候也是按照写入顺序进行解析。

实现一个简单的样例,

IDENTIFIER = b'\x91'

class Foobar(object):
	def __init__(self, value):
		self.value = value

def encode_foobar(name, value, dummy0, dummy1):
	return IDENTIFIER + name + bson._PACK_FLOAT(value.value)

def decode_foobar(data, position, dummy0, dummy1, dummy2):
	step = 8
	end = position + step
	value = bson._UNPACK_FLOAT(data[position:end])[0]
	return Foobar(value), end

在不更新之前执行,

print bson.BSON(bson.BSON.encode({'foobar': Foobar(42)})).decode()

会遇到如下异常,

bson.errors.InvalidDocument: cannot convert value of type <class '__main__.Foobar'> to bson

在更新处理之后,

update_bson(IDENTIFIER, Foobar, encode_foobar, decode_foobar)
print bson.BSON(bson.BSON.encode({'foobar': Foobar(42)})).decode()

可以获得正常输出,

{u'foobar': <__main__.Foobar object at 0x02376570>}

处理cbson

在存在cbson时,bson/__init__.py中的编解码函数会被替换成cbson中的实现。因此无法再动态修改,如果想继续使用cbson,那么就需要修改bson/_cbsonmodule.c中的实现。

encode数据需要修改,

static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
                                    int type_byte, PyObject* value,
                                    unsigned char check_keys,
                                    const codec_options_t* options)

decode数据需要修改,

static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer,
                           unsigned* position, unsigned char type,
                           unsigned max, const codec_options_t* options)

在上述两个函数中分别实现对应的编解码逻辑即可。如果不想每次增加新类型都需要重新编译修改cbson中的实现,那么就可以考虑在cbson中遇到这些自定义数据时反向调用脚本层中的处理逻辑。