Capitalized fields with POJO Input Handlers for Java AWS Lambda

Viewed 1901

According to the documentation described here: http://docs.aws.amazon.com/lambda/latest/dg/java-programming-model-req-resp.html one can create its own POJO to serialise input and output for Java AWS Lambda.

However it appears that it doesn't properly work for input requests where fields are capitalised. For instance, the format of input for a custom resource lambda looks like:

{"RequestType":"Create", 
"ServiceToken":"arn:aws:lambda:....", 
"ResponseURL":"https://cloudformation-custom-resource-response-e...",
...}

This can be easily tested via this simple MCVE code:

package test;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class TestLambda implements RequestHandler<TestLambda.TestEvent, String> {

    private static final Logger logger = LogManager.getLogger(TestLambda.class);

    @Override
    public String handleRequest(TestEvent event, Context context) {
         logger.debug(event.toString());    
         return null;
    }

    public static final class TestEvent {
        private String key1;

        private String Key2;

        private String key3;

        public String getKey1() {
            return key1;
        }

        public void setKey1(String key1) {
            this.key1 = key1;
        }

        public String getKey2() {
            return Key2;
        }

        public void setKey2(String key2) {
            Key2 = key2;
        }

        public String getKey3() {
            return key3;
        }

        public void setKey3(String key3) {
            this.key3 = key3;
        }

        @Override
        public String toString() {
            return "TestEvent{" +
                    "key1='" + key1 + '\'' +
                    ", Key2='" + Key2 + '\'' +
                    ", key3='" + key3 + '\'' +
                    '}';
        }
    }
}

Then create a test in AWS Console for this lambda and pass there as a request the following json:

{
  "key3": "value3",
  "Key2": "value2",
  "Key1": "value1"
}

The result in logs will be:

2017-11-06 09:30:13 16849696-c2d5-11e7-80c3-150a37863c42 DEBUG TestLambda:15 - TestEvent{key1='null', Key2='null', key3='value3'}

Is there any way to deserialise this input without dealing with a raw byte stream as they suggest in that topic?

You shouldn't rely on any other features of serialization frameworks such as annotations. If you need to customize the serialization behavior, you can use the raw byte stream to use your own serialization.

This seems to me to be a great limitation of Java AWS Lambdas if we can not freely create a POJO for any type of event.

3 Answers

In the pojo, make the fields public and exactly the same case as the json fields. This means you should have upper camel case fields e.g.,

public class TestEvent {
    public String Key1;

    public String Key2;

    public String key3;
}

I cannot explain why this works, but I just tried this today based on a work colleague's suggestion and it works. I know it doesn't look elegant. But at least it's fewer lines of code than deserializing streams.

I've been in the same issue for a long time, and finally found the answer:

There is a blueprint for Lambda : https://github.com/awslabs/aws-apigateway-lambda-authorizer-blueprints/blob/0f7f3d933741a48c08c85feff267793f60b61a60/blueprints/java/src/io/AuthPolicy.java#L68

So basically what you need to do is to override the get method of your JSON. And have a Map<String, Object> returned in this method. Now you can have capitalized key inside the map to resolve the problem:

public Map<String, Object> getPolicyDocument() {
        Map<String, Object> serializablePolicy = new HashMap<>();
        serializablePolicy.put(VERSION, policyDocumentObject.Version);
        Statement[] statements = policyDocumentObject.getStatement();
        Map<String, Object>[] serializableStatementArray = new Map[statements.length];
        for (int i = 0; i < statements.length; i++) {
            Map<String, Object> serializableStatement = new HashMap<>();
            AuthPolicy.Statement statement = statements[i];
            serializableStatement.put(EFFECT, statement.Effect);
            serializableStatement.put(ACTION, statement.Action);
            serializableStatement.put(RESOURCE, statement.getResource());
            serializableStatement.put(CONDITION, statement.getCondition());
            serializableStatementArray[i] = serializableStatement;
        }
        serializablePolicy.put(STATEMENT, serializableStatementArray);
        return serializablePolicy;
    }

The property on your Java bean is still key2, all lower case, because the property name visible to the serialization framework is derived from the getter, not the private field name. So it still goes into the input event looking for key2 not Key2.

Related