Search code examples
springspring-el

Spring Expression Language (SpEL) for json access with list


I'm trying to understand why this doesn't work?

  context.addPropertyAccessor(new JsonPropertyAccessor());

  Object j = mapper.readTree(
"{\"foo\": {\"bar\": [ { \"fizz\": 5, \"buzz\": 6, \"doo\": 9 }, {\"fizz\": 7}, {\"fizz\": 8} ] } })");

System.out.println(evaluate(j, "foo.bar.![doo == 9]"));

It always prints:

[false, false, false]

I need to check if any of the doo contains a 9.


Solution

  • If you don't need use SPEL necessarily you can do it easily with Jayway JsonPath

    Include dependency:

    <dependency>
        <groupId>com.jayway.jsonpath</groupId>
        <artifactId>json-path</artifactId>
        <version>2.4.0</version>
    </dependency>
    

    Then use the JsonPath and query:

    Srgin json = "{\"foo\": {\"bar\": [ { \"fizz\": 5, \"buzz\": 6, \"doo\": 9 },
        {\"fizz\": 7}, {\"fizz\": 8, \"doo\": 6} ] } })";
    
    JSONArray array = JsonPath.read(json, "foo.bar[?(@.doo==9)]");
    
    // now array is a List of size 1, since second "doo" is not equal 9
    // using array.get(0) you can get first object as a HashMap<String, Object>
    

    By the link mentioned above you can find more details about JsonPath queries.


    If you still need to integrate JsonPath with SPEL you can add parsing function to the evaluation context. Fortunately you already use spring-integration which has JsonPathUtils class with evaluate(String, String) method.

    StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
    Method method = BeanUtils.resolveSignature("evaluate", JsonPathUtils.class);
    evaluationContext.registerFunction("jsonPath", method);
    
    String json = "{\"foo\": {\"bar\": [ { \"fizz\": 5, \"buzz\": 6, \"doo\": 9 }, " +
                "{\"fizz\": 7}, {\"fizz\": 8, \"doo\": 7} ] } })";
    evaluationContext.setVariable("foo", json);    
    
    ExpressionParser parser = new SpelExpressionParser();
    Expression expression = parser.parseExpression("#jsonPath(#foo, 'foo.bar[?(@.doo>6)]')");
    Object result = expression.getValue(evaluationContext);
    //now result contains still the same JSONArray
    

    And finally I figured out the reason of your issue. First of all: guess what returns the expression foo.bar.doo? It's not a String or Integer value. It returns an instance of ToStringFriendlyJsonNode. So doo==9 will always give you false since they can't be equal. Moreover you can't use > or < because it's not allowed to compare Object and Integer. Therefore you need to cast to suitable type before comparison.

     expression.getValue("foo.bar.![doo!=null and new Integer(doo.toString()) == 9]");
    

    Also we need to check if doo exists that's why we need to check for null. ToStringFriendlyJsonNode can provide only string value. That's why we have to use ToString(). As you see native SPEL is inconvenient and too verbose because it's not supposed to be used for complex Json queries.