Home > Mobile >  Implement a function over an array nested inside an Hashmap to perform aggregate operation using SpE
Implement a function over an array nested inside an Hashmap to perform aggregate operation using SpE

Time:09-30

I have a payload like

String payload = "{\"array\":[{\"a\":\"b\",\"c\":10},{\"a\":\"b\",\"c\":20},{\"a\":\"b\",\"c\":30}],\"boolean\":true,\"color\":\"gold\",\"null\":null,\"number\":123,\"object\":{\"a\":\"b\",\"c\":\"d\"},\"string\":\"HelloWorld\"}";

This string payload is mapped as a HashMap using

Map map = new ObjectMapper().readValue(payload, HashMap.class);

Now, I want to add a functionality that enables me to get value and perform some operations using just a String Expression passed.

For Eg:

  1. doSomething(data, "data.array") // Output : [{\"a\":\"b\",\"c\":10},{\"a\":\"b\",\"c\":20},{\"a\":\"b\",\"c\":30}] -> as an Object

  2. doSomething(data, "data.array.stream().map(item -> item.get('c')).sum()") // Output : 10 20 30 = 60

I want to achieve something like this. Can someone pls help, how can I implement this.

CodePudding user response:

SpEL cannot understand lambda expressions (->).

You can use streams but without lambdas.

follow this answer for a detailed explanation: https://stackoverflow.com/a/48842973/7972621

CodePudding user response:

Library Josson has the capability to get value and perform some operations using just a String Expression passed.

https://github.com/octomix/josson

Deserialization

String payload = "{\"array\":[{\"a\":\"b\",\"c\":10},{\"a\":\"b\",\"c\":20},{\"a\":\"b\",\"c\":30}],\"boolean\":true,\"color\":\"gold\",\"null\":null,\"number\":123,\"object\":{\"a\":\"b\",\"c\":\"d\"},\"string\":\"HelloWorld\"}";
Josson data = Josson.fromJsonString(payload);

Query an array node

JsonNode node = data.getNode("array");
System.out.println(node.toPrettyString());

Output

[ {
  "a" : "b",
  "c" : 10
}, {
  "a" : "b",
  "c" : 20
}, {
  "a" : "b",
  "c" : 30
} ]

Evaluate data by a function

JsonNode node = data.getNode("array.c.sum()");
System.out.println(node.toPrettyString());

Output

60.0

CodePudding user response:

SPeL cannot understand lambda expressions (->), but there is an implicit method.

You can register and use lambdas as SPeL #functions.

You can extend SPeL by registering user defined functions that can be called within the expression string. The function is registered with the StandardEvaluationContext using the method.

We can write an example that takes the SPeL code directly or contains the StandardEvaluationContext:

public class SPeLExample {

    public static void main(String[] args) {
        try {
            // JSON payload.
            String payload = "...";

            // Response 1 test.
            List<HashMap<String, Object>> response1 = (List<HashMap<String, Object>>)
                    doSomething(payload, "#root['array']");
            System.out.println("Response 1 => "   response1);

            // Response 2 test.
            StandardEvaluationContext standardEvaluationContext = new StandardEvaluationContext();
            standardEvaluationContext.registerFunction("getMapItem",
                    SPeLExample.class.getMethod("getMapItem", String.class));
            standardEvaluationContext.registerFunction("toInteger",
                    SPeLExample.class.getMethod("toInteger"));
            Integer response2 = (Integer) doSomething(payload,
                    "#root['array'] == null ? null "  
                            ": #root['array'].stream()"  
                            "   .map(#getMapItem('c'))"  
                            "   .mapToInt(#toInteger()).sum()",
                    standardEvaluationContext);
            System.out.println("Response => "   response2);
        } catch (Exception e) { throw new RuntimeException(e); }
    }

    public static Object doSomething(String payload,
                                     String expressionString) {
        return doSomething(payload, expressionString, null);
    }

    public static Object doSomething(String payload, 
                                     String expressionString, 
                                     StandardEvaluationContext standardEvaluationContext) {
        try {
            Map<String, Object> map = new ObjectMapper()
                    .readValue(payload, HashMap.class);
            ExpressionParser expressionParser = new SpelExpressionParser();
            Expression expression = expressionParser.parseExpression(expressionString);
            if (standardEvaluationContext == null)
                return expression.getValue(map);
            return expression.getValue(standardEvaluationContext, map);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Function<LinkedHashMap, Object> getMapItem(String key) {
        return o -> o.get(key);
    }

    public static ToIntFunction<Object> toInteger() {
        return o -> Integer.valueOf((int)o);
    }
}

I added two lambda functions getMapItem and toInteger, I registered them in StandardEvaluationContext.

I rewrote your "data.array.stream().map(item -> item.get('c')).sum()" code to handle special functions like this:

"#root['array'] == null ? null : #root['array'].stream().map(#getMapItem('c')).mapToInt(#toInteger()).sum()"
  • Related