简体   繁体   中英

AWS Lambda json deserialization with jackson annotations

I'm calling an aws lambda with a json body. So the fields of the json are with different name from the ones in the POJO. So what I did is to add @JsonProperty on the fields to tell jackson what are the names in json. But for some reason it seems that it doesn't recognize them and all the fields are null. If I pass a json with the same field names as the POJO it's working. Here's my class:

public class Event implements Identifiable {

    @JsonProperty("distinct_id")
    private String distinctId;

    @JsonProperty("user_id")
    private Integer userId;

    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime eventDateTime;

    //Here are the getters and setters
}

If I pass

{"distinct_id":"123", "user_id":123, "dt":"2017-01-04T08:45:04+00:00"} 

all the fields are null and with distinctId, userId, eventDateTime it's serializing ok with the exception that it also doesn't recognize my custom serializers/deserializers but this actually is the same problem.

My conclusion is that for some reason the aws jackson is not working with the annotations but it doesn't make sense.

So I found a way to do this. You need to implement RequestStreamHandler which gives you input and output streams which you can work with:

import com.amazonaws.services.lambda.runtime.RequestStreamHandler

public class ChartHandler implements RequestStreamHandler {
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
        DeserializationClass deserializedInput = objectMapper.readValue(inputStream, DeserializationClass.class)
        objectMapper.writeValue(outputStream, deserializedInput); //write to the outputStream what you want to return
    }

}

Having the input and output streams makes you independent of the format and frameworks you use to parse it.

Take a look at this quote from AWS documentation:

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.

From: https://docs.aws.amazon.com/lambda/latest/dg/java-programming-model-req-resp.html

It sounds like you have version mismatch between annotation types, and databind ( ObjectMapper ): both MUST be the same major version. Specifically, Jackson 1.x annotations work with Jackson 1.x databind; and 2.x with 2.x.

Difference is visible via Java package: Jackson 1.x uses org.codehaus.jackson , whereas Jackson 2.x uses com.fasterxml.jackson . Make sure to import right annotations for ObjectMapper you use.

为属性创建getter方法并将@JsonProperty放在getter方法上。

I had this same issue and needed MyCustomClass to be taken in and out of the Lambda Function correctly so that it can be passed through my State Machine in the Step Function without any hiccups.

Building off what Hristo Angelov posted, I was able to get a solution that worked for me and I'm posting it hoping that it will help others that were stuck like I was:

import java.io.InputStream;
import java.io.OutputStream;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

public class StaticSearchPagingLambdaFunctionHandler implements RequestStreamHandler {

    LambdaLogger logger = null;
    MyCustomClass myCustomClass = null;

    // Register the JavaTimeModule for LocalDate conversion
    ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());

    @Override
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) {    

            myCustomClass = objectMapper.readValue(inputStream, MyCustomClass .class);

            // ...
            // Do stuff with myCustomClass
            // ...

            objectMapper.writeValue(outputStream, myCustomClass);
    }
}

Even though the JSON string will print out differently with the ObjectMapper writing to the OutPutStream , when the next lambda function takes it in while going through the Step Function , it will still get converted to LocalDate correctly.

Make sure that in MyCustomClass your toString() method prints correctly. My toString() method looks like this:

import java.time.LocalDate;

import org.json.JSONObject;

public class SimpleSearch {

    private LocalDate startDate;
    private LocalDate endDate;

    // ...
    // Getters and Setters for the LocalDate variables
    // ...

    @Override
    public String toString() {
        return new JSONObject(this).toString();
    }

    public SimpleSearch() {}
}

then your JSON printouts will always look like this when it gets sent to the lambda and not that other crazy Jackson format:

{
    "startDate": "2018-11-01",
    "endDate": "2018-11-16"
}

Some of the Maven dependencies I used:

<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20180813</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.9.7</version>
</dependency>

Hopefully AWS fixes the Jackson conversions to be reciprocal, to and from JSON, so that we wouldn't have to resort to these custom conversions anymore.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM