user1986244
user1986244

Reputation: 267

Variable api JSON responses to bind to Java Object using Jackson

Currently, I am calling third party api which can give the following two variable responses. I am working in springboot and using Jackson for transformation of REST responses to Java object. The two variables responses are the following
error, and success data response combined

{
    "errorCount": 2,
    "errorIndices": [
        0,
        1
    ],
    "data": [
        {
            "errorCode": 901,
            "errorMessage": "IBad data: Check the data",
            "errorData": "xxxx"
        },
        {
            "errorCode": 901,
            "errorMessage": "IBad data: Check the data",
            "errorData": "XZY"
        },
        "fun now"
    ]
}

In case of all success

"{"errorCount": 0,
"errorIndices": [],
"data": [
    "fun now",
    "try later"
]
}

The below class I created works for all success scenario,

public class ApiResponse {
 @JsonProperty
 private int errorCount;
  @JsonProperty
 private int[] errorIndices;
  @JsonProperty
 private String[] data;

......

}

but I am unable to convert the first scenario where error and success results are combined. Is it possible to create java object that can combined the two scenarios using Jackson api? I need some direction on how to achieve that.

Upvotes: 0

Views: 423

Answers (2)

user1986244
user1986244

Reputation: 267

My target was to use Jackson api, and @Raymond approach is using another library. I found custom deserialization appro[ach here][1] I used the SingleAwareListDeserializer from the article and updated it to the following. In my case the deserializer in case of String response it is just pushing the String to the list.

  if (JsonToken.START_ARRAY.equals(token)) {
            while (p.nextToken() != null) {
                if (JsonToken.START_OBJECT.equals(p.currentToken())) {
                    list.add(deserializeObject(p));
                } else  if (JsonToken.VALUE_STRING.equals(p.currentToken())) {
                 //added this part in case of string response
                list.add(p.getValueAsString());
                } 
            }
        }

I have the following classes to map the response.

public class Data {

    private Data() {
        
    }

    private String errorCode;
    private String errorMessage;
    private String errorData;
    
    public String getErrorCode() {
        return errorCode;
    }
    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }
    public String getErrorMessage() {
        return errorMessage;
    }
    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }
    public String getErrorData() {
        return errorData;
    }
    public void setErrorData(String errorData) {
        this.errorData = errorData;
    }

    
    
}

Below ApiResponse

public class ApiResponse {

    int errorCount;
    int[] errorIndices;
    
    @JsonDeserialize(using = SingleAwareListDeserializer.class)
    List<Data> data;
    
    public int getErrorCount() {
        return errorCount;
    }
    public void setErrorCount(int errorCount) {
        this.errorCount = errorCount;
    }
    public int[] getErrorIndices() {
        return errorIndices;
    }
    public void setErrorIndices(int[] errorIndices) {
        this.errorIndices = errorIndices;
    }

    public List<Data> getData() {
        return data;
    }
    public void setData(List<Data> data) {
        this.data = data;
    }
    
}

For testing I used the following,

public void deserializeTest() {
       //case response has a generic List with Data object type and string //value mix
        String objStringWithError = "\n"
                + "{\n"
                + "    \"errorCount\": 2,\n"
                + "    \"errorIndices\": [\n"
                + "        0,\n"
                + "        1\n"
                + "    ],\n"
                + "    \"data\": [\n"
                + "        \"try now\",\n"
                + "        {\n"
                + "            \"errorCode\": 901,\n"
                + "            \"errorMessage\": \"IBad data: Check the data\",\n"
                + "            \"errorData\": \"xxxx\"\n"
                + "        },\n"
                + "        {\n"
                + "            \"errorCode\": 901,\n"
                + "            \"errorMessage\": \"IBad data: Check the data\",\n"
                + "            \"errorData\": \"XZY\"\n"
                + "        },\n"
                + "        \"fun now\"\n"
                + "    ]\n"
                + "}";
//case response have errorcount, errorIndices fields and array of Strings
        String objStringWithNoError = "{\"errorCount\": 0,\n"
                + "\"errorIndices\": [],\n"
                + "\"data\": [\n"
                + "    \"fun now\",\n"
                + "    \"try later\"\n"
                + "]}";
//case response is only array of strings
        String stringArray = "{\n"
                + "\"data\": [\n"
                + "    \"fun now\",\n"
                + "    \"try later\"\n"
                + "]}";

        ObjectMapper mapper = new ObjectMapper();
        ApiResponse model = null;
        try {
            model = mapper.readValue(objStringWithError, ApiResponse.class);
        } catch (JsonMappingException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        } catch (JsonProcessingException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }

The deserializer works in all three cases; I only mentioned first case in my initial question. I have a mix of data types-- a Data type object and String type object, but if the response is all String I still also getting List of String in the ApiResponse data field which converted to List object. What I understood is that the deserializer is creating a generic List object and assigning it to List. I could not create field under Data class to hold string value when Deserializer is within this condition. if(JsonToken.VALUE_STRING.equals(p.currentToken())) [1]: https://dzone.com/articles/consuming-variable-responses-in-jackson

Upvotes: 0

Raymond Choi
Raymond Choi

Reputation: 1271

One possible solution is to transform the JSON structure, separate String and Object elements before deserialization.

https://github.com/octomix/josson

Josson josson = Josson.fromJsonString(
    "{" +
    "    \"errorCount\": 2," +
    "    \"errorIndices\": [" +
    "        0," +
    "        1" +
    "    ]," +
    "    \"data\": [" +
    "        {" +
    "            \"errorCode\": 901," +
    "            \"errorMessage\": \"IBad data: Check the data\"," +
    "            \"errorData\": \"xxxx\"" +
    "        }," +
    "        {" +
    "            \"errorCode\": 901," +
    "            \"errorMessage\": \"IBad data: Check the data\"," +
    "            \"errorData\": \"XZY\"" +
    "        }," +
    "        \"fun now\"" +
    "    ]" +
    "}");
JsonNode node = josson.getNode("field(data[isText()]*, errors: data[isObject()]*)");
System.out.println(node.toPrettyString());

Output

{
  "errorCount" : 2,
  "errorIndices" : [ 0, 1 ],
  "data" : [ "fun now" ],
  "errors" : [ {
    "errorCode" : 901,
    "errorMessage" : "IBad data: Check the data",
    "errorData" : "xxxx"
  }, {
    "errorCode" : 901,
    "errorMessage" : "IBad data: Check the data",
    "errorData" : "XZY"
  } ]
}

Deserialization

I tested this without error.

JsonNode node = josson.getNode("field(data[isText()]*, errors: data[isObject()]*)");
ApiResponse apiResponse = new ObjectMapper().readerFor(ApiResponse.class).readValue(node);

Error.java

public class Error {
    int errorCode;
    String errorMessage;
    String errorData;

    public Error() {
    }

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }

    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }

    public void setErrorData(String errorData) {
        this.errorData = errorData;
    }
}

ApiResponse.java

public class ApiResponse {
    int errorCount;
    int[] errorIndices;
    String[] data;
    Error[] errors;

    public ApiResponse() {
    }

    public void setErrorCount(int errorCount) {
        this.errorCount = errorCount;
    }

    public void setErrorIndices(int[] errorIndices) {
        this.errorIndices = errorIndices;
    }

    public void setData(String[] data) {
        this.data = data;
    }

    public void setErrors(Error[] errors) {
        this.errors = errors;
    }
}

Upvotes: 1

Related Questions