Node.js mongoose返回第二级的所有非空字段

时间:2017-11-07 05:19:38

标签: javascript node.js mongodb mongoose

想在No​​de.js和mongoose上再次向您学习。

我定义了mongoose模式,findOne()返回如下文档。实际文档中的“资源”下有更多元素。

{
    "metadata": {"isActive": true, "isDeleted": false },
    "test": "123",
    "resource": {
        "id": "59e94f3f6d5789611ce9926f",
        "resourceType": "Patient",
        "active": true,
        "gender": "male",
        "birthDate": "2000-01-01T00:00:00.000Z",
        "extension": [
            {
                "url": "hxxp://example.com/fhir/StructureDefinition/patient-default-bundle",
                "valueCodeableConcept": {
                    "code": "sys",
                    "display": ""
                }
            }
        ],
        "link": [],
        "careProvider": [],
        "communication": [],
        "animal": {
            "genderStatus": {
                "coding": []
            },
            "breed": {
                "coding": []
            },
            "species": {
                "coding": []
            }
        },
        "contact": []
    }
}

问题:如何选择“资源”下的所有非空字段?

我的预期结果如下,即'resource'元素下的所有非空字段。

{
  "id": "59e94f3f6d5789611ce9926f",
  "resourceType": "Patient",
  "active": true,
  "gender": "male",
  "birthDate": "2000-01-01T00:00:00.000Z",
  "extension": [
      {
          "url": "hxxp://example.com/fhir/StructureDefinition/patient-default-bundle",
          "valueCodeableConcept": {
              "code": "sys",
              "display": ""
          }
      }
  ]
}

我目前的编码:

module.exports.findById = function (req, res, next) {
    var resourceId = req.params.resourceId;
    var resourceType = req.params.resourceType;
    var thisModel = require('mongoose').model(resourceType);

    console.log("findById is being called by the API [" + resourceType + "][" + resourceId + "]");
    thisModel.findOne(
        {'resource.id': resourceId, 'metadata.isActive': true, 'metadata.isDeleted': false},
        'resource -_id',
        function(err, doc) {
            if (err) {
                globalsvc.sendOperationOutcome(res, resourceId, "Error", "findOne() Not Found", err, 404);
            }
            else {
                if (doc) {
                    sendJsonResponse(res, 200, doc);
                }  else {
                    delete doc._id;
                    globalsvc.sendOperationOutcome(res, resourceId, "Error", "Id: [" + resourceId + "] Not Found", err, 404);
                }
            }
        }
    );
}

2 个答案:

答案 0 :(得分:2)

如前所述,实际上不首先将空数组存储在MongoDB集合中,而不是在返回数据时尝试处理它们。您实际上只能通过在最新版本中使用聚合框架功能(然后仍然不递归)从返回的结果中省略它们,或者以允许服务器返回整个对象然后在传递它们之前从文档中剥离这些属性为止。

所以我真的认为这是修复数据的两步过程。

更改架构以省略空阵列

当然你说你在模式中有更多的字段,但从我所看到的我可以给你一些例子。基本上你需要在数组为default的任何内容上加上undefined值。只列出一些作为您的架构的部分内容:

"resource": {
  "extension": {
    "type": [{
      "url": String,
      "valueCodeableConcept": {
        "code": String,
        "display": String
      }
    ],
    "default": undefined
  },
  "link": { "type": [String], "default": undefined },
  "animal": {
    "genderStatus": { 
      "coding": { "type": [String], "default": undefined }
    },
    "breed": {
      "coding": { "type": [String], "default": undefined }
    }
  }
}

这应该给你一般的想法。使用这些"default"值,当没有提供其他数据时,mongoose不会尝试写入空数组。一旦通过记录每个数组定义来修复模式,就不会再创建空数组了。

修剪数据

这应该是"一次性的"删除仅托管空数组的所有属性的操作。这意味着你也真的想摆脱每个内部键下只有一个空数组的属性,比如"animals"属性。

所以我只想做一个简单的列表来重写数据:

const MongoClient = require('mongodb').MongoClient;

const uri = 'mongodb://localhost/test',
      collectionName = 'junk';

function returnEmpty(obj) {
  var result = {};

  Object.keys(obj).forEach(k => {
    if ( typeof(obj[k]) === "object" && obj[k].constructor === Object ) {
      let temp = returnEmpty(obj[k]);
      if (Object.keys(temp).length !== 0)
        result[k] = temp;
    } else if ( !((Array.isArray(obj[k]) && obj[k].length > 0)
      || !Array.isArray(obj[k]) ) )
    {
      result[k] = obj[k];
    }
  });

  return result;
}

function stripPaths(obj,cmp) {
  var result = {};

  Object.keys(obj).forEach( k => {
    if ( Object.keys(obj[k]).length !== Object.keys(cmp[k]).length ) {
      result[k] = stripPaths(obj[k], cmp[k]);
    } else {
      result[k] = "";
    }
  });

  return result;
}

function dotNotate(obj,target,prefix) {
  target = target || {};
  prefix = prefix || "";

  Object.keys(obj).forEach( key => {
    if ( typeof(obj[key]) === 'object' ) {
      dotNotate(obj[key], target, prefix + key + '.');
    } else {
      target[prefix + key] = obj[key];
    }
  });

  return target;
}

function log(data) {
  console.log(JSON.stringify(data, undefined, 2))
}

(async function() {

  let db;

  try {

    db = await MongoClient.connect(uri);

    let collection = db.collection(collectionName);

    let ops = [];
    let cursor = collection.find();

    while ( await cursor.hasNext() ) {
      let doc = await cursor.next();
      let stripped = returnEmpty(doc);
      let res = stripPaths(stripped, doc);
      let $unset = dotNotate(res);

      ops.push({
        updateOne: {
          filter: { _id: doc._id },
          update: { $unset }
        }
      });

      if ( ops.length > 1000 ) {
        await collection.bulkWrite(ops);
        ops = [];
      }
    }

    if ( ops.length > 0 ) {
      await collection.bulkWrite(ops);
      log(ops);
      ops = [];
    }


  } catch(e) {
    console.error(e);
  } finally {
    db.close();
  }

})();

这基本上会为您的集合中的每个文档生成一个操作,以bulkWrite()$unset提供空属性的路径。

对于您提供的文档,更新将如下所示:

[
  {
    "updateOne": {
      "filter": {
        "_id": "5a0151108204f6bce9baf86f"
      },
      "update": {
        "$unset": {
          "resource.link": "",
          "resource.careProvider": "",
          "resource.communication": "",
          "resource.animal": "",
          "resource.contact": ""
        }
      }
    }
  }
]

这基本上标识了所有具有空数组的属性,甚至删除了"animal"下的所有键,因为每个键都有一个空数组,如果我们删除了那个键就只是一个空对象子键。因此,我们删除整个密钥及其子密钥。

一旦运行,所有这些不需要的密钥将从存储的文档中删除,然后任何查询将只返回实际定义的数据。因此,短期内这是一项长期收益的小工作。

操纵结果

当然对于懒惰,你可以简单地应用用于返回路径的基本函数,使用反向逻辑删除返回对象的路径:

function returnStripped(obj) {
  var result = {};

  Object.keys(obj).forEach(k => {
   if ( typeof(obj[k]) === "object" && obj[k].constructor === Object ) {
     var temp = returnStripped(obj[k]);
     if (Object.keys(temp).length !== 0)
       result[k] = temp;
   } else if ( ((Array.isArray(obj[k]) && obj[k].length > 0) || !Array.isArray(obj[k])) ) {
     result[k] = obj[k];
   }
  });

  return result;
}


db.collection.find().map(returnStripped)

只需从结果中删除不需要的密钥。

它可以完成这项工作,但这里获得的更大收益来自实际修复架构和永久更新数据。

答案 1 :(得分:0)

我认为你可以接受这个。

thisModel.findOne({ extension: { $gt: [] } })