使用 mongodb 和 mongoose (nodejs) 的不同字段名称映射多个数据源



我正在开发一个网站,该网站将容纳来自不同来源的大量数据。源可能具有许多相同的数据(存在一些冗余(,但 json 中的字段可能在这里和那里有所不同。这些源来自 REST API,都是 json。

我的问题是,如何使用猫鼬模式映射这些不同的来源?我希望它尽可能简单,就像某种表一样,我只需要添加新源的字段名称,以及它映射到我的数据库中的字段。我正在使用快递/节点。

下面是两个源的示例,以及我的架构规范。

例如。

第一个来源:

{
"address":"123 abc st"
}

第二个来源:

{
"addr":"123 abc st"
}

我的架构:

{
address:{
type: String,
required: true
}

您可以创建一个 JSON 文件来存储映射,并在存储对象时检索它。

例如,创建一个 JSON 文件mappings.json

{
"addr": "address",
"address": "address"
}

然后签入代码,如果该属性对应于映射的属性。

let mappings = require('mappings.json');
let MyModel = require('models/myModel');
function createNewObjectFromInput (input, callback) {
let options = {};
for (let property in input) {
if (input.hasOwnProperty(property) && mappings[property]) {
options[mappings[property]] = input[property];
}
}
let newObject = new MyModel(options);
newObject.save(callback);
}

为了使跨多个源(可能出现属性冲突(更加健壮,您可以为每个源创建这样的 JSON 文件,或者为每个源创建一个单独的映射,如下所示:

{
"firstSource": { 
"addr": "address
},
"secondSource": {
"address": "address"
}
}

只需检查您的输入数据来自哪个来源。

通过使用 model.discriminator(( 方法,您可以尝试在架构之间创建继承。在此处查看猫鼬文档。

您在这里似乎想要的是简单地设置一个带有"别名"名称的"虚拟"字段。这允许您为字段使用不同的名称,但这些名称将"重新映射"到架构中定义的正确名称。

作为完整演示:

const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/test';
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
const addressSchema = new Schema({
address: { type: String, required: true }
});
// asssign a virtual to the "mapped" name
addressSchema.virtual('addr')
.get(function() { return this.address })
.set(function(v) { this.address = v });
const Address = mongoose.model('Adddress', addressSchema);
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
// Raw documents don't work as the setter is ignored
try {
await Address.insertMany(
[{ adddress: "123 abc st" },{ addr: "123 abc st" }]
);
} catch(e) {
console.error(e);
}
// Creating via new works as expected
let doc = new Address({ addr: "123 abc st" });
log(doc);
// Using Array.map over the objects to cast them works fine
await Address.insertMany(
[{ address: "123 abc str" },{ addr: "245 abc st" }]
.map(Address)
);
let inserted = await Address.find();
log({ inserted });
mongoose.disconnect();
} catch (e) {
console.error(e)
} finally {
process.exit()
}
})();

这演示了预期的结果:

Mongoose: adddresses.remove({}, {})
{ ValidationError: Adddress validation failed: address: Path `address` is required.
at ValidationError.inspect (/home/neillunn/projects/alias/node_modules/mongoose/lib/error/validation.js:56:24)
at formatValue (util.js:430:38)
at inspect (util.js:324:10)
at format (util.js:191:12)
at Console.warn (console.js:145:21)
at /home/neillunn/projects/alias/index.js:37:15
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
errors:
{ address:
{ ValidatorError: Path `address` is required.
at new ValidatorError (/home/neillunn/projects/alias/node_modules/mongoose/lib/error/validator.js:25:11)
at validate (/home/neillunn/projects/alias/node_modules/mongoose/lib/schematype.js:805:13)
at /home/neillunn/projects/alias/node_modules/mongoose/lib/schematype.js:854:11
at Array.forEach (<anonymous>)
at SchemaString.SchemaType.doValidate (/home/neillunn/projects/alias/node_modules/mongoose/lib/schematype.js:814:19)
at /home/neillunn/projects/alias/node_modules/mongoose/lib/document.js:1712:9
at _combinedTickCallback (internal/process/next_tick.js:131:7)
at process._tickCallback (internal/process/next_tick.js:180:9)
message: 'Path `address` is required.',
name: 'ValidatorError',
properties: [Object],
kind: 'required',
path: 'address',
value: undefined,
reason: undefined,
'$isValidatorError': true } },
_message: 'Adddress validation failed',
name: 'ValidationError' }
{
"_id": "5b110aa571311d0a70cd3c88",
"address": "123 abc st"
}
Mongoose: adddresses.insertMany([ { _id: 5b110aa571311d0a70cd3c89, address: '123 abc str', __v: 0 }, { _id: 5b110aa571311d0a70cd3c8a, address: '245 abc st', __v: 0 } ], {})
Mongoose: adddresses.find({}, { fields: {} })
{
"inserted": [
{
"_id": "5b110aa571311d0a70cd3c89",
"address": "123 abc str",
"__v": 0
},
{
"_id": "5b110aa571311d0a70cd3c8a",
"address": "245 abc st",
"__v": 0
}
]
}

为了完成此操作,除了架构中的adddress字段之外,所有定义的内容都在架构上设置另一个虚拟属性:

addressSchema.virtual('addr')
.get(function() { return this.address })
.set(function(v) { this.address = v });

这应该非常简单,因为get()函数用于在您实际请求addr时从address读取,并且每当某些东西尝试为addr提供值然后将该值放入adddress时,set()函数当然会应用。

唯一的问题是,这些"虚拟"不会自动应用于您可能尝试使用它们的"任何地方"。它们与new Model()一起查找,但它们不会自动应用于Model.create()Model.insertMany()

对于这种情况,常见的解决方法是,当您有一个原始输入文档列表时,通过"首先"通过模型构造函数运行它们来"强制转换"它们,正如列表所示:

// Using Array.map over the objects to cast them works fine
await Address.insertMany(
[{ address: "123 abc str" },{ addr: "245 abc st" }]
.map(Address)
);

然后,数据将被正确地重写为要插入的两个文档中的address,这发生在required验证器触发之前。

另一种情况是在架构上使用alias属性:

const addressSchema = new Schema({
address: { type: String, required: true, alias: 'addr' }
});

这实际上以完全相同的方式工作,而无需编写完整的"虚拟"方法。您可能会认为这是执行相同操作的较短选项,但您可能应该意识到,预期目的实际上是"相反",即您希望在架构中定义"较短"的名称,并将"较长"的名称用于赋值。

此外,您可以根据需要添加任意数量的"虚拟",但只能在架构定义中使用alias赋值"一次"。因此,如果您有不同的数据源,这些数据源都对要存储的内容使用不同的名称,那么使用较长的形式可能是更好的方法。

两者都记录在猫鼬模式文档中

  • 虚拟吸气手和二传手
  • 别名字段

最新更新