1

I'm forced to use the aggregation framework and the project operation of Spring Data MongoDb.

What I'd like to do is creating an array of object as a result of a project operation.

Considering this intermediate aggregation result:

{
  "processes": [
    {
      "id": "101a",
      "assignees": [
        {
          "id": "201a",
          "username": "carl93"
        },
        {
          "id": "202a",
          "username": "susan"
        }
      ]
    },
    {
      "id": "101b",
      "assignees": [
        {
          "id": "201a",
          "username": "carl93"
        },
        {
          "id": "202a",
          "username": "susan"
        }
      ]
    }
  ]
}

I'm trying to get for each process, all the assignee usernames and ids. Hence, what I want to obtain is something like this:

[
  {
    "results": [
      {
        "id": "201a",
        "value": "carl93",
        "parentObjectId": "101a"
      },
      {
        "id": "202a",
        "value": "susan",
        "parentObjectId": "101a"
      },
      {
        "id": "201a",
        "value": "carl93",
        "parentObjectId": "101b"
      },
      {
        "id": "202a",
        "value": "susan",
        "parentObjectId": "101b"
      }
    ]
  }
]

To reach this goal I'm using 2 nested VariableOperators.mapItemsOf obtaining:

org.springframework.data.mapping.MappingException: Cannot convert [Document{{id= 201a, value= carl93, parentObjectId= 101a}}, Document{{id= 202a, value = susan, parentObjectId= 101a}}]
of type class java.util.ArrayList into an instance of class java.lang.Object! 
Implement a custom Converter<class java.util.ArrayList, class java.lang.Object> and register it with the CustomConversions.

Here's the code that I'm currently using:

new ProjectionOperation().and(
    VariableOperators.mapItemsOf("processes")
      .as("pr")
      .andApply(
           VariableOperators.mapItemsOf("$pr.ownership.assignees")
               .as("ass")
               .andApply(aggregationOperationContext -> {
           Document document = new Document();

           document.append("id", "$$ass.id");
           document.append("value", "$$ass.username");
           document.append("parentObjectId", "$$pr.id");

           return document;
          })
    )
).as("results");

The code produces this:

[ 
  [
    {
      "id": "201a",
      "value": "carl93",
      "parentObjectId": "101a"
    },
    {
      "id": "202a",
      "value": "susan",
      "parentObjectId": "101a"
    }
  ], 
  [
    {
      "id": "201a",
      "value": "carl93",
      "parentObjectId": "101b"
    },
    {
      "id": "202a",
      "value": "susan",
      "parentObjectId": "101b"
    }
  ]
]

As you can see there are 2 nested arrays, [[],[]]. This is the reason why the exception is thrown. Nevertheless what I want to obtain is just one array, adding all the objects in it (possibly without duplicates or null values). I've tried the addToSet operator and other aggregtion operators, without any success.

4
  • I don't see why you get nulls and 4 elements when you're sample document has only two elements. Are you sure you have the right document in the question ? Commented Sep 25, 2018 at 15:35
  • Good point, I've written this question starting from a more complicated example. The null values are not compliant to the initial example. Hence, just forget about the null values. What is messing things ups is the array of arrays structure that comes from the 2 nested VariableOperators.mapItemsOf() Commented Sep 25, 2018 at 15:39
  • 1
    did you intend to include results field for each array element in the processes ? That is the only difference I see between output. Commented Sep 25, 2018 at 15:49
  • I've updated the question, with the proper goal I want to reach and removing the null values that weren't compliant to the initial example. Commented Sep 25, 2018 at 15:58

1 Answer 1

3

Use $reduce with $concatArrays to join the arrays.

 new ProjectionOperation().and(
    ArrayOperators.arrayOf("processes")
      .reduce(ArrayOperators.ConcatArrays.arrayOf("$$value").concat(
           VariableOperators.mapItemsOf("$$this.ownership.assignees")
               .as("ass")
               .andApply(aggregationOperationContext -> {
           Document document = new Document();
           document.append("id", "$$ass.id");
           document.append("value", "$$ass.username");
           document.append("parentObjectId", "$$this.id");
           return document;
          })
    )).startingWith(Arrays.asList())
).as("results");
Sign up to request clarification or add additional context in comments.

11 Comments

Nice approach! But I obtain java.lang.IllegalArgumentException: Invalid reference '$$value'!
I have a misplaced braces. Try now and it works in my version 2.0.4 spring mongo jar.
I had noticed the misplaced brace. I obtain java.lang.IllegalArgumentException: Invalid reference '$$value'!. What is the purpose of $$value? Moreover, I've tried to place the content of concat inside arrayOf with bad results :(
$$value keeps accumulated values as we concat array elements inside $reduce operation. $$this refers to the current element in iteration. More here. What is your spring mongo version ? Do you have the right imports ?
<artifactId>spring-data-mongodb</artifactId> <version>2.0.8.RELEASE</version>
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.