How to map a JSON string which includes members named long and short

Viewed 107

I need to map a JSON string which includes values named long and short:

"status": {
   "long": "Finished",
   "short": "F",
   "elapsed": 90
}

I tried the following class:

public class Status {

    @JsonProperty("long")
    public String _long;
    @JsonProperty("short")
    public String _short;
    @JsonProperty("elapsed")
    public Object elapsed;

}

with the command:

objectMapper.readValue(resBody, Response.class);

response contains the status part:

{
    "response": {
        "id": 157016,
        "timezone": "UTC",
        "date": "2019-08-10T11:30:00+00:00",
        "timestamp": 1565436600,
        "status": {
            "long": "Long Value",
            "short": "LV",
            "elapsed": 20
        }
    }
}

But still I get the following error:

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "long"

How can this be fixed? I do not have control on the JSON format.

4 Answers

Well, it is obviously not a perfect solution to the problem, but one may find it as a helpfull workaround:

Since I don't care about these values, I'll just rename their names and adapt the class members name accordingly:

on the json string resBody I get as a response I will do the following

@NotNull 
private static String mitigateLongAndShortValueNames(String resBody) { 
   resBody = resBody.replaceAll("\"long\":", "\"longValue\":"); 
   resBody = resBody.replaceAll("\"short\":", "\"shortValue\":"); 
   return resBody; 
} 

and change

public class Status {

    @JsonProperty("long")
    public String _long;
    @JsonProperty("short")
    public String _short;
    @JsonProperty("elapsed")
    public Object elapsed;

}

to

public class Status {

    public String longValue;
    public String shortValue;
    public Object elapsed;

}

It worked for me!

One of the ways to solve the problem is select the json part you are interested combining the ObjectMapper#readTree method that converts your json to a JsonNode object and then select the part of the JsonNode object you are looking for with the JsonNode#at method which matches the /response/status path expression like below:

//it contains only the status labelled node of your json
JsonNode statusNode = mapper.readTree(json).at("/response/status");   

After you can use the ObjectMapper#treeToValue method to convert the JsonNode to your Status class obtaining the expected result:

JsonNode statusNode = mapper.readTree(json).at("/response/status");
Status status = mapper.treeToValue(statusNode, Status.class);
//ok, it prints {"long":"Long Value","short":"LV","elapsed":20}
System.out.println(mapper.writeValueAsString(status));

The full exception message should look something like this: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "long" (class ...), not marked as ignorable (n known properties: ...)

If it is the case that you're not interested in certain properties, it's easier and cleaner to ignore those during parsing. Either with an annotation on the data class or a DeserializationFeature on the objectmapper.

See Jackson with JSON: Unrecognized field, not marked as ignorable

Extending the comment: there's no problem with Status and "status":... parts, @JsonProperty() is meant to handle the renaming. You probably pass the wrong class to readValue().

public class TestClass {
    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        TestClass test= mapper.readValue(
                "{\r\n" + 
                "    \"response\": {\r\n" + 
                "        \"id\": 157016,\r\n" + 
                "        \"status\": {\r\n" + 
                "            \"long\": \"Long Value\",\r\n" + 
                "            \"short\": \"LV\",\r\n" + 
                "            \"elapsed\": 20\r\n" + 
                "        }\r\n" + 
                "    }\r\n" + 
                "}",TestClass.class); // <-- here, it's not Response, but an outer class
        System.out.println(test.response.id);
        System.out.println(test.response.status._long);
    }
    public Response response;         // <-- containing "response"
    static public class Response {
        public long id;
        public Status status;
    }
    static public class Status {
        @JsonProperty("long")
        public String _long;
        @JsonProperty("short")
        public String _short;
        @JsonProperty("elapsed")
        public Object elapsed;
    }
}

Displays

157016
Long Value

as expected. Kept only id from the outer object for the sake of brevity.

Related