Skip to content

Distinguish between optional field and field with null value #1558

@TWiStErRob

Description

@TWiStErRob

JSON allows us to express fields that shouldn't be there and fields that could have a null value. These are different things. In Java with cannot have missing fields, so we use null to represent both of these states. We're interoping with an external non-Java party that has a schema like this:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "nullProp": { "type": [ "string", "null" ] },
    "optionalProp": { "type": "string" }
  },
  "required": [ "nullProp" ]
}

because optionalProp is not listed, it can be omitted from the JSON, but if it exists, it must have a non-null value:

  • valid: { "nullProp": null, "optionalProp": "my value" }
  • valid: { "nullProp": null }
  • valid: { "nullProp": "null value", "optionalProp": "my value" }
  • invalid: { "nullProp": null, "optionalProp": null } // optionalProp cannot be null
  • invalid: { } // missing required nullProp

we need to send valid JSON that adheres to the schema (manual tests www.jsonschemavalidator.net)

In Java we represent this with:

class Data {
	@Nullable String nullProp;
	@Nullable String optionalProp;
}

but the serialization behavior needs to be different for these two fields. We've looked at all the combinations of serializeNulls and @JsonAdapter, but couldn't figure out how to omit the optional field when its null, while keeping the null field there if it's null.

Here's a test we've been trying to pass with no luck:

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
import com.google.gson.*;

class OptionalTest {

	class Data {
		// TODO @JsonAdapter(NullAdapter.class)?
		String nullProp;
		// TODO @JsonAdapter(OptionalAdapter.class)?
		String optionalProp;
	}

	private Gson gson;

	@BeforeEach void setUp() {
		gson = new GsonBuilder()
				// TODO .serializeNulls()?
				// TODO .registerTypeAdapterFactory()?
				.create();
	}

	@Test void bothNull() {
		Data data = new Data();
		data.nullProp = null;
		data.optionalProp = null;

		assertEquals("{\"nullProp\":null}", gson.toJson(data));
	}

	@Test void filledContents() {
		Data data = new Data();
		data.nullProp = "some";
		data.optionalProp = "thing";

		assertEquals("{\"nullProp\":\"some\",\"optionalProp\":\"thing\"}", gson.toJson(data));
	}
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions