Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AWS lambda java response does not support nested objects?

I'm new to Java so this may not be related to AWS lambda at all. However, lambda takes such liberties with input/output objects that I'm assuming it's the culprit here.

I'm building my first lambda function and want to return a simple JSON structure (simplified further for this example):

{
  "document" : "1",
  "person" : { "name" : "John Doe" }
}

However, when lambda serializes the JSON it always sets "person" to a blank object!

{
  "document": "1",
  "person": {}
}

Here is my code in full:

 - test1.java
package handler_test;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class test1 implements RequestHandler<String, ResponseClass> {
    @Override
    public ResponseClass handleRequest(String input, Context context) {
      return new ResponseClass();
    }
}

 - ResponseClass.java
package handler_test;
import org.json.JSONException;
import org.json.JSONObject;
public class ResponseClass {
    String document;
    JSONObject person;

    public String getdocument() {
        return "1";
    }

    public JSONObject getperson() {
        try {
            return new JSONObject("{ \"name\" : \"John Doe\" }");
        } catch (JSONException e1) {
            System.out.println("error creating jsonobject");
            return null;
        }
    }

    public ResponseClass() {
    }
}

I've tried this with dozens of variations, using objects instead of JSONObjects, converting getperson output to a string (which works, if I wanted a string), etc. If I store the values and print them to the logger, it's fine. But as soon as I try to return it through lambda it goes pear-shaped. I've combed the 'net and can't find anything more on AWS-lambda java response objects beyond Amazon's "greetings" sample code, which just includes two strings. Any suggestions greatly appreciated!

like image 320
Gregg Avatar asked Apr 13 '16 12:04

Gregg


2 Answers

I solved this using the stream handlers, and not only does it work but you have more control and less code! I used gson for JSON serialization/deserialization and the Apache IOUtils for converting inputsteam to a string. As I'd already written it using the Request and Response classes, I continued using those, although I was able to get rid of all the getter and setter code.

Two notes: 1. gson will output all non-null attributes of the Response class, even if they're declared private, so if there are values you don't want to spit back be sure to set them to null before the final line. 2. When using Eclipse IDE with the AWS plugin, it will not upload the code to AWS unless it can find a RequestHandler! Thus I have a stub function that is immediately overridden.

import com.google.gson.*;
import org.apache.commons.io.IOUtils;
import com.amazonaws.services.lambda.runtime.*;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;

public class Intel implements RequestStreamHandler, RequestHandler<Object, Object> {
    @Override
    public Object handleRequest(Object input, Context context) {
        return null;
    }

    @Override
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
        Request req = new Gson().fromJson(IOUtils.toString(inputStream, "UTF-8"), Request.class);
        Response resp = new ResponseClass();
            resp.id = 1;
            resp.person.name = req.name;
            et_home_phone(req.name);
        outputStream.write(new Gson().toJson(resp).getBytes(Charset.forName("UTF-8")));
    }

    private void get_home_phone(String name) {
        // call external API -- stub example!  Assumes only one phone returned
        // in the format { "number" : "123-456-7890", "type" = "home" }
        // gson magic assures they get copied to the keys of the same name
        HttpGet httpGet = new HttpGet(phoneURL + "/" + name));
        HttpResponse httpResponse = client.execute(httpGet);
        resp.phone[0] = new Gson().fromJson(IOUtils.toString(httpResponse .getEntity().getContent(), "UTF-8"), Response.Phone.class);
    }
}

public class Response {
    public class Person {
        String name;
    }
    public class Phone {
        String number;
        String type;
    }
    public Integer id;
    public Person person = new Person();
    public Phone[] phone = new Phone[5];
}
like image 69
Gregg Avatar answered Oct 04 '22 22:10

Gregg


You can skip JSONObject and use a POJO for the nested class. However, naming according to conventions is important here. Make sure your accessor methods are named using camel case (get + name of property capitalized). Try this:

public class ResponseClass {
    String document;
    Person person;

    public String getDocument() {
        return "1";
    }

    public Person getPerson() {
        return person;
    }
}

class Person {
    String name;
    public String getName() {
        return name;
    }
}
like image 27
ataylor Avatar answered Oct 04 '22 22:10

ataylor