mongodb

MongoDB - Convert nested array values


I have the following structure (subset):

{"values"=>
  [{"foo"=>"12"},
   {"foo"=>"34"},
   {"foo"=>"56"},...]}

and I want to convert the fields from string to decimal like this

db.foos.updateMany(
      { "values" => { "$type": "array" } },
      { "$set": { "values.$[element].foo": { "$toDecimal": "$values.$[element].foo" } } },
      arrayFilters: [{ "element.foo": { "$type": "string" } }]
    )

but I'm getting a literal { "$toDecimal": "$values.$[element].foo" } } written to the field. (I need the string filter to be able to add a $trim later (something like { '$toDecimal': { '$trim': { input: "$foo" } } }))

What am I doing wrong?


Solution

  • In MongoDB update query, you can't update value by referencing the field name.

    You should look for update with aggregation pipeline.

    db.foos.update({
      "values": {
        "$type": "array"
      }
    },
    [
      {
        "$set": {
          "values": {
            "$map": {
              "input": "$values",
              "in": {
                "$cond": {
                  "if": {
                    "$eq": [
                      {
                        "$type": "$$this.foo"
                      },
                      "string"
                    ]
                  },
                  "then": {
                    "$mergeObjects": [
                      "$$this",
                      {
                        "foo": {
                          "$toDecimal": {
                            "$trim": {
                              "input": "$$this.foo"
                            }
                          }
                        }
                      }
                    ]
                  },
                  "else": "$$this"
                }
              }
            }
          }
        }
      }
    ])
    

    Demo @ MongoPlayground


    For aggregate query:

    1. $match - Filter document
    2. $set - Set values field. Iterate element in values array and transform foo value to Decimal.
    3. $merge - Merge the output of the aggregation pipeline to collection by _id.
    db.foos.aggregate([
      {
        "$match": {
          "$expr": {
            "$eq": [
              {
                "$type": "$values"
              },
              "array"
            ]
          }
        }
      },
      {
        "$set": {
          "values": {
            "$map": {
              "input": "$values",
              "in": {
                "$cond": {
                  "if": {
                    "$eq": [
                      {
                        "$type": "$$this.foo"
                      },
                      "string"
                    ]
                  },
                  "then": {
                    "$mergeObjects": [
                      "$$this",
                      {
                        "foo": {
                          "$toDecimal": {
                            "$trim": {
                              "input": "$$this.foo"
                            }
                          }
                        }
                      }
                    ]
                  },
                  "else": "$$this"
                }
              }
            }
          }
        }
      },
      {
        "$merge": {
          "into": "foos",
          "on": "_id",
          "whenMatched": "replace"
        }
      }
    ])
    

    Demo @ Mongo Playground