用新创建的替换复杂数据结构中的对象 ID
Replacing object ids in the complex data structure with newly created ones
我有一个可以深度嵌套的数据结构如下:
{
'field1' : 'id1',
'field2':{'f1':'id1', 'f2':'id2', 'f3':'id3'},
'field3':['id1','id2', 'id3' ,' id4'],
'field4':[{'f1': 'id3', 'f2': 'id4'}, ...]
.....
}
以此类推。嵌套可以是任意深度,可以是任意数据结构的排列组合。
这里的id1,id2,id3是用bson库生成的ObjectId等价的字符串,从mongoDB查询得到记录。
我想替换这些 ID 的所有出现,即; id1,id2...新创建的。
替换必须是这样的,id1 必须在所有地方用一个新的 id 替换为相同的新创建的 id,并且对其他 id 也相同。
为阐明上述内容:
如果 id5 是新生成的 id,那么 id5 必须出现在 id1 出现的所有地方等等。
这是我执行上述操作的解决方案:
import re
from bson import ObjectId
from collections import defaultdict
import datetime
class MutableString(object):
'''
class that represents a mutable string
'''
def __init__(self, data):
self.data = list(data)
def __repr__(self):
return "".join(self.data)
def __setitem__(self, index, value):
self.data[index] = value
def __getitem__(self, index):
if type(index) == slice:
return "".join(self.data[index])
return self.data[index]
def __delitem__(self, index):
del self.data[index]
def __add__(self, other):
self.data.extend(list(other))
def __len__(self):
return len(self.data)
def get_object_id_position_mapping(string):
'''
obtains the mapping of start and end positions of object ids in the record from DB
:param string: string representation of record from DB
:return: mapping of start and end positions of object ids in record from DB (dict)
'''
object_id_pattern = r'[0-9a-f]{24}'
mapping = defaultdict(list)
for match in re.finditer(object_id_pattern, string):
start = match.start()
end = match.end()
mapping[string[start:end]].append((start,end))
return mapping
def replace_with_new_object_ids(mapping, string):
'''
replaces the old object ids in record with new ones
:param mapping: mapping of start and end positions of object ids in record from DB (dict)
:param string: string representation of record from DB
:return:
'''
mutable_string = MutableString(string)
for indexes in mapping.values():
new_object_id = str(ObjectId())
for index in indexes:
start,end = index
mutable_string[start:end] = new_object_id
return eval(str(mutable_string))
def create_new(record):
'''
create a new record with replaced object ids
:param record: record from DB
:return: new record (dict)
'''
string = str(record)
mapping = get_object_id_position_mapping(string)
new_record = replace_with_new_object_ids(mapping, string)
return new_record
简而言之,我将字典转换为字符串,然后替换了 id 并完成了工作。
但我觉得这绝对不是最好的方法,因为如果我没有合适的导入(在这种情况下是日期时间),eval() 可能会失败,而且我可能没有对象类型的信息(例如日期时间等)事先在数据库中。
我什至尝试了这里描述的 nested_lookup 方法 https://github.com/russellballestrini/nested-lookup/blob/master/nested_lookup/nested_lookup.py
但无法完全按照我想要的方式工作。
有一个更好的方法吗?
注意:我不关心效率。我想要的只是自动化用新 ID 替换这些 ID 的过程,以节省手动这样做的时间。
编辑 1:我将使用从 MongoDB 获得的记录作为参数调用 create_new()
编辑 2:该结构可以将日期时间等其他对象作为值
例如:
{
'field1' : 'id1',
'field2':{'f1':datetime.datetime(2017, 11, 1, 0, 0), 'f2':'id2', 'f3':'id3'},
'field3':['id1','id2', 'id3' ,' id4'],
'field4':[{'f1': 'id3', 'f2': datetime.datetime(2017,11, 1, 0 , 0)}, ...]
.....
}
其他对象必须保持不变,只有 id 必须被替换
您可以使用递归函数向下钻取到嵌套在输入数据结构中的字符串。
def replace_ids(obj, new_ids=None):
if new_ids is None:
new_ids = {}
if isinstance(obj, dict):
return {key: replace_ids(value, new_ids) for key, value in obj.items()}
if isinstance(obj, list):
return [replace_ids(item, new_ids) for item in obj]
if isinstance(obj, str):
if obj not in new_ids:
new_ids[obj] = generate_new_id()
return new_ids[obj]
return obj
generate_new_id
是一个函数,应该确定您要如何生成新 ID。
在 michaelrccurtis 回答的帮助下,我可以做到以下几点:
from bson import ObjectId
import datetime
def replace_ids(obj, new_ids=None):
if new_ids is None:
new_ids = {}
if isinstance(obj, dict):
return {key: replace_ids(value, new_ids) for key, value in obj.items()}
if isinstance(obj, list):
return [replace_ids(item, new_ids) for item in obj]
if isinstance(obj, str):
if obj not in new_ids:
new_ids[obj] = generate_new_id(obj)
return new_ids[obj]
if isinstance(obj, ObjectId):
return ObjectId()
return obj
def generate_new_id(obj):
if is_valid_objectid(obj):
return str(ObjectId())
return obj
def is_valid_objectid(objid):
if not objid:
return False
obj = ObjectId()
return obj.is_valid(objid)
a = {'_id':ObjectId('5a37844dcf2391c87fb4f845'),
'a':'5a37844dcf2391c87fb4f844',
'b':[{'a':'5a37844dcf2391c87fb4f844', 'b':'ABCDEFGH'},{'a':'5a37844dcf2391c87fb4f846', 'b':'abc123456789111111'}],
'c':['5a37844dcf2391c87fb4f846','5a37844dcf2391c87fb4f844','5a37844dcf2391c87fb4f847'],
'd':datetime.datetime(2017,11,1,0,0)
}
b = replace_ids(a)
print(b)
输出:
{ '_id': ObjectId('5a380a08147e37122d1ee7de'),
'a': '5a380a08147e37122d1ee7e2',
'c': ['5a380a08147e37122d1ee7e0', '5a380a08147e37122d1ee7e2',
'5a380a08147e37122d1ee7e4'],
'b': [{'b': 'ABCDEFGH', 'a': '5a380a08147e37122d1ee7e2'}, {'b':
'abc123456789111111', 'a': '5a380a08147e37122d1ee7e0'}],
'd': datetime.datetime(2017, 11, 1, 0, 0)
}
注意:答案可能因您机器上的 ID 生成而异。
向 michaelrccurtis 大声疾呼以获得惊人的答案
我有一个可以深度嵌套的数据结构如下:
{
'field1' : 'id1',
'field2':{'f1':'id1', 'f2':'id2', 'f3':'id3'},
'field3':['id1','id2', 'id3' ,' id4'],
'field4':[{'f1': 'id3', 'f2': 'id4'}, ...]
.....
}
以此类推。嵌套可以是任意深度,可以是任意数据结构的排列组合。
这里的id1,id2,id3是用bson库生成的ObjectId等价的字符串,从mongoDB查询得到记录。 我想替换这些 ID 的所有出现,即; id1,id2...新创建的。
替换必须是这样的,id1 必须在所有地方用一个新的 id 替换为相同的新创建的 id,并且对其他 id 也相同。
为阐明上述内容: 如果 id5 是新生成的 id,那么 id5 必须出现在 id1 出现的所有地方等等。
这是我执行上述操作的解决方案:
import re
from bson import ObjectId
from collections import defaultdict
import datetime
class MutableString(object):
'''
class that represents a mutable string
'''
def __init__(self, data):
self.data = list(data)
def __repr__(self):
return "".join(self.data)
def __setitem__(self, index, value):
self.data[index] = value
def __getitem__(self, index):
if type(index) == slice:
return "".join(self.data[index])
return self.data[index]
def __delitem__(self, index):
del self.data[index]
def __add__(self, other):
self.data.extend(list(other))
def __len__(self):
return len(self.data)
def get_object_id_position_mapping(string):
'''
obtains the mapping of start and end positions of object ids in the record from DB
:param string: string representation of record from DB
:return: mapping of start and end positions of object ids in record from DB (dict)
'''
object_id_pattern = r'[0-9a-f]{24}'
mapping = defaultdict(list)
for match in re.finditer(object_id_pattern, string):
start = match.start()
end = match.end()
mapping[string[start:end]].append((start,end))
return mapping
def replace_with_new_object_ids(mapping, string):
'''
replaces the old object ids in record with new ones
:param mapping: mapping of start and end positions of object ids in record from DB (dict)
:param string: string representation of record from DB
:return:
'''
mutable_string = MutableString(string)
for indexes in mapping.values():
new_object_id = str(ObjectId())
for index in indexes:
start,end = index
mutable_string[start:end] = new_object_id
return eval(str(mutable_string))
def create_new(record):
'''
create a new record with replaced object ids
:param record: record from DB
:return: new record (dict)
'''
string = str(record)
mapping = get_object_id_position_mapping(string)
new_record = replace_with_new_object_ids(mapping, string)
return new_record
简而言之,我将字典转换为字符串,然后替换了 id 并完成了工作。
但我觉得这绝对不是最好的方法,因为如果我没有合适的导入(在这种情况下是日期时间),eval() 可能会失败,而且我可能没有对象类型的信息(例如日期时间等)事先在数据库中。
我什至尝试了这里描述的 nested_lookup 方法 https://github.com/russellballestrini/nested-lookup/blob/master/nested_lookup/nested_lookup.py
但无法完全按照我想要的方式工作。 有一个更好的方法吗?
注意:我不关心效率。我想要的只是自动化用新 ID 替换这些 ID 的过程,以节省手动这样做的时间。
编辑 1:我将使用从 MongoDB 获得的记录作为参数调用 create_new()
编辑 2:该结构可以将日期时间等其他对象作为值 例如:
{
'field1' : 'id1',
'field2':{'f1':datetime.datetime(2017, 11, 1, 0, 0), 'f2':'id2', 'f3':'id3'},
'field3':['id1','id2', 'id3' ,' id4'],
'field4':[{'f1': 'id3', 'f2': datetime.datetime(2017,11, 1, 0 , 0)}, ...]
.....
}
其他对象必须保持不变,只有 id 必须被替换
您可以使用递归函数向下钻取到嵌套在输入数据结构中的字符串。
def replace_ids(obj, new_ids=None):
if new_ids is None:
new_ids = {}
if isinstance(obj, dict):
return {key: replace_ids(value, new_ids) for key, value in obj.items()}
if isinstance(obj, list):
return [replace_ids(item, new_ids) for item in obj]
if isinstance(obj, str):
if obj not in new_ids:
new_ids[obj] = generate_new_id()
return new_ids[obj]
return obj
generate_new_id
是一个函数,应该确定您要如何生成新 ID。
在 michaelrccurtis 回答的帮助下,我可以做到以下几点:
from bson import ObjectId
import datetime
def replace_ids(obj, new_ids=None):
if new_ids is None:
new_ids = {}
if isinstance(obj, dict):
return {key: replace_ids(value, new_ids) for key, value in obj.items()}
if isinstance(obj, list):
return [replace_ids(item, new_ids) for item in obj]
if isinstance(obj, str):
if obj not in new_ids:
new_ids[obj] = generate_new_id(obj)
return new_ids[obj]
if isinstance(obj, ObjectId):
return ObjectId()
return obj
def generate_new_id(obj):
if is_valid_objectid(obj):
return str(ObjectId())
return obj
def is_valid_objectid(objid):
if not objid:
return False
obj = ObjectId()
return obj.is_valid(objid)
a = {'_id':ObjectId('5a37844dcf2391c87fb4f845'),
'a':'5a37844dcf2391c87fb4f844',
'b':[{'a':'5a37844dcf2391c87fb4f844', 'b':'ABCDEFGH'},{'a':'5a37844dcf2391c87fb4f846', 'b':'abc123456789111111'}],
'c':['5a37844dcf2391c87fb4f846','5a37844dcf2391c87fb4f844','5a37844dcf2391c87fb4f847'],
'd':datetime.datetime(2017,11,1,0,0)
}
b = replace_ids(a)
print(b)
输出:
{ '_id': ObjectId('5a380a08147e37122d1ee7de'),
'a': '5a380a08147e37122d1ee7e2',
'c': ['5a380a08147e37122d1ee7e0', '5a380a08147e37122d1ee7e2',
'5a380a08147e37122d1ee7e4'],
'b': [{'b': 'ABCDEFGH', 'a': '5a380a08147e37122d1ee7e2'}, {'b':
'abc123456789111111', 'a': '5a380a08147e37122d1ee7e0'}],
'd': datetime.datetime(2017, 11, 1, 0, 0)
}
注意:答案可能因您机器上的 ID 生成而异。
向 michaelrccurtis 大声疾呼以获得惊人的答案