Kong / unirest-java

Unirest in Java: Simplified, lightweight HTTP client library.
http://kong.github.io/unirest-java/
MIT License
2.59k stars 593 forks source link

Nulls Are not Serialized #357

Closed haroon-sheikh closed 4 years ago

haroon-sheikh commented 4 years ago

Describe the bug A clear and concise description of what the bug is.

To Reproduce Steps to reproduce the behavior:

  1. Provide as much of a code sample as possible. Unirest is pretty small so it you should be able to mostly copy-paste into the issue

Mapper

public class Mapper implements ObjectMapper {
    private Gson om;
    public Mapper() {
        this(new Gson());
    }
    public Mapper(Gson om) {
        this.om = om;
    }
    @Override
    public <T> T readValue(String value, Class<T> valueType) {
        return om.fromJson(value, valueType);
    }
    @Override
    public <T> T readValue(String value, GenericType<T> genericType) {
        return om.fromJson(value, genericType.getType());
    }
    @Override
    public String writeValue(Object value) {
        return om.toJson(value);
    }
}

Option 1:

Gson gson = new GsonBuilder()
        .setPrettyPrinting()
        .serializeNulls()
        .create();

Unirest.config().setObjectMapper(new Mapper(gson));

HttpResponse<JsonNode> response = Unirest.get("my/url/with/null/values")
        .headers(headers)
        .asJson();

Expected = {"key1": "value", "key2": null} Actual = {"key1": "value"}

Option 1:

Gson gson = new GsonBuilder()
        .setPrettyPrinting()
        .serializeNulls()
        .create();

HttpResponse<JsonNode> response = Unirest.get("my/url/with/null/values")
        .withObjectMapper(new Mapper(gson))
        .headers(headers)
        .asJson();

Expected = {"key1": "value", "key2": null} Actual = {"key1": "value"}

Screenshots If applicable, add screenshots to help explain your problem.

Environmental Data:

Additional context Add any other context about the problem here.

ryber commented 4 years ago

Hi @haroon-sheikh

I have some questions. You are setting serializeNulls on the Gson Object Mapper. That setting will make sure that when you have a Java Object and it has a null value, it will serialize it as null.

But the object mapper is not used by the Unirest Code you are showing. You aren't posting an object to the user, and you are not mapping the response to an object. asJson does not use the ObjectMapper because it's not mapping the response to an Object, only to a generic JSON Tree

note that serializeNulls doesn't really have anything to mapping JSON to an object, it is used when serializing an object to a JSON string. Again, you are not posting an object so this would never happen. Per the Google documentation:

/**
   * Configure Gson to serialize null fields. By default, Gson omits all fields that are null
   * during serialization.
   */
haroon-sheikh commented 4 years ago

Thanks for looking into it @ryber.

I would like to have the null values in the Java object. Any ideas what would be the easiest/best approach for achieving this?

I thought just creating Gson gson = new GsonBuilder().serializeNulls().create(); and passing to the custom mapper would work. But obviously i'm missing something.

If I understand it correctly, i would need to map it to object asObject along with withObjectMapper?

ryber commented 4 years ago

In what Java Object? the JsonNode? It does have the key, it's just null and if you toString it it will omit the key, but it does exist in there:

    @Test
    public void nullObjectsOnParse() {
        JSONObject obj = new JSONObject("{\"key1\": \"value\", \"key2\": null}");

        assertTrue(obj.has("key2"));
        assertEquals(null, obj.get("key2"));
    }

are you asking for the JsonNode's toString operation to include nulls?

haroon-sheikh commented 4 years ago

Yep, jsonnode.getBody() to include nulls in the String.

ryber commented 4 years ago

If all you are interested in is the string you could just call .asString().getBody() and get the raw JSON body. There is no need to go through some library if all you want it raw JSON.

haroon-sheikh commented 4 years ago

If all you are interested in is the string you could just call .asString().getBody() and get the raw JSON body. There is no need to go through some library if all you want it raw JSON.

That makes sense. We're in middle of applying an update to Unirest version from 1 to 3. The problem we have is that the same method is expecting a json object as well as raw string. And the removal of getRawBody in the latest versions isn't helping. We'll put together a workaround until, the changes can be refactored to work separately with both.

Thank you for your support. :)

ryber commented 4 years ago

The JSON implementation implementation in unirest is a cleanroom implementation of org.json and should have all the same behavior. I checked with org.json and found that it does serialize nulls, so I'm reopening this issue to fix that defect

ryber commented 4 years ago

The JSON implementation implementation in unirest is a cleanroom implementation of org.json and should have all the same behavior. I checked with org.json and found that it does serialize nulls, so I'm reopening this issue to fix that defect

ryber commented 4 years ago

3.7.04 is released with the change @haroon-sheikh

haroon-sheikh commented 4 years ago

Thank you @ryber. It's working as expected.