Python 用新创建的对象ID替换复杂数据结构中的对象ID

Python 用新创建的对象ID替换复杂数据结构中的对象ID,python,mongodb,replace,objectid,Python,Mongodb,Replace,Objectid,我有一个可以深度嵌套的数据结构,如下所示: { 'field1' : 'id1', 'field2':{'f1':'id1', 'f2':'id2', 'f3':'id3'}, 'field3':['id1','id2', 'id3' ,' id4'], 'field4':[{'f1': 'id3', 'f2': 'id4'}, ...] ..... } 依此类推。嵌套可以是任何深度,也可以是任何数据结构的排列和组合 这里,id1、id2、id3是使用bson库生成的ObjectId

我有一个可以深度嵌套的数据结构,如下所示:

{
 '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,完成了工作。

但我觉得这肯定不是最好的方法,因为如果我没有合适的导入(本例中为datetime),并且我可能事先没有数据库中对象类型的信息(如datetime等),eval()可能会失败

我甚至尝试了这里描述的嵌套查找方法

但是我不能让它按照我想要的方式工作。 有更好的方法吗

注意:效率不是我关心的问题。我想做的就是用新ID自动替换这些ID,以节省手动操作的时间

编辑1:我将使用从MongoDB获得的记录作为参数调用create_new()

编辑2:结构可以将其他对象(如datetime)作为值 例如:

 {
 '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\u new\u id
是一个确定如何生成新id的函数。

在michaelccurtis
的帮助下,我可以执行以下操作:

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生成而异


向Michaelccurtis大声喊出一个惊人的答案

你认为什么是最好的方法?使用递归方法还是使用字符串替换方法?字符串替换方法更容易受到您不想更改的内容的影响,并且对输入数据的id结构的更改不太稳定,因此,我更喜欢递归方法。如果存在值为其他对象的键值对,上述答案是否成立?有关更多信息,请参阅编辑2。当前函数尝试转换所有字符串。任何不是id的字符串都可以保持不变(例如,将generate_new_id()更改为transform_id(obj)),或者添加额外的过滤器。我仍然赞成这种方法,因为你们在改变什么方面仍然受到很大限制。