Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a good way to allow only one non null field in an object

Tags:

java

I want to write a class with more than 1 fields of different types but at any time, there is one and only one field of an instance object having non null value.

What I did so far does not look really clean.

class ExclusiveField {      private BigInteger numericParam;     private String stringParam;     private LocalDateTime dateParam;      public void setNumericParam(BigInteger numericParam) {         unsetAll();         this.numericParam = Objects.requireNonNull(numericParam);     }      public void setStringParam(String stringParam) {         unsetAll();         this.stringParam = Objects.requireNonNull(stringParam);     }      public void setDateParam(LocalDateTime dateParam) {         unsetAll();         this.dateParam = Objects.requireNonNull(dateParam);     }      private void unsetAll() {         this.numericParam = null;         this.stringParam = null;         this.dateParam = null;     } } 

Does Java support this pattern somehow or is there a more decent way to do it?

like image 587
HieuHT Avatar asked May 08 '19 09:05

HieuHT


People also ask

How do you ignore null fields in JSON response?

You can ignore null fields at the class level by using @JsonInclude(Include. NON_NULL) to only include non-null fields, thus excluding any attribute whose value is null. You can also use the same annotation at the field level to instruct Jackson to ignore that field while converting Java object to json if it's null.

What is a non-null field?

Defining a field in your schema as NonNull means that GraphQL promises to always return a value when the field is queried. It allows clients to do fewer response validation checks in their code and improves static analysis.

What is JSON include non-null?

Include. NON_NULL: Indicates that only properties with not null values will be included in JSON. Include. NON_EMPTY: Indicates that only properties that are not empty will be included in JSON. Non-empty can have different meaning for different objects such as List with size zero will be considered as empty.


2 Answers

The simplest approach for an object to have only one non-null field, is to actually have only one field and assume all others to be null implicitly. You only need another tag field, to determine which field is non-null.

Since in your example, all alternatives seem to be about the type of the value, the type itself could be the tag value, e.g.

class ExclusiveField {     private Class<?> type;     private Object value;      private <T> void set(Class<T> t, T v) {         value = Objects.requireNonNull(v);         type = t;     }     private <T> T get(Class<T> t) {         return type == t? t.cast(value): null;     }      public void setNumericParam(BigInteger numericParam) {         set(BigInteger.class, numericParam);     }      public BigInteger getNumericParam() {         return get(BigInteger.class);     }      public void setStringParam(String stringParam) {         set(String.class, stringParam);     }      public String getStringParam() {         return get(String.class);     }      public void setDateParam(LocalDateTime dateParam) {         set(LocalDateTime.class, dateParam);     }      public LocalDateTime getDateParam() {         return get(LocalDateTime.class);     } } 

If the type is not the only differentiator, you need to define distinct key values. An enum would be a natural choice, but unfortunately, enum constants can not provide the type safety. So, the alternative would look like:

class ExclusiveField {     private static final class Key<T> {         static final Key<String>        STRING_PROPERTY_1 = new Key<>();         static final Key<String>        STRING_PROPERTY_2 = new Key<>();         static final Key<BigInteger>    BIGINT_PROPERTY   = new Key<>();         static final Key<LocalDateTime> DATE_PROPERTY     = new Key<>();     }     private Key<?> type;     private Object value;      private <T> void set(Key<T> t, T v) {         value = Objects.requireNonNull(v);         type = t;     }      @SuppressWarnings("unchecked") // works if only set() and get() are used     private <T> T get(Key<T> t) {         return type == t? (T)value: null;     }      public void setNumericParam(BigInteger numericParam) {         set(Key.BIGINT_PROPERTY, numericParam);     }      public BigInteger getNumericParam() {         return get(Key.BIGINT_PROPERTY);     }      public void setString1Param(String stringParam) {         set(Key.STRING_PROPERTY_1, stringParam);     }      public String getString1Param() {         return get(Key.STRING_PROPERTY_1);     }      public void setString2Param(String stringParam) {         set(Key.STRING_PROPERTY_2, stringParam);     }      public String getString2Param() {         return get(Key.STRING_PROPERTY_2);     }      public void setDateParam(LocalDateTime dateParam) {         set(Key.DATE_PROPERTY, dateParam);     }      public LocalDateTime getDateParam() {         return get(Key.DATE_PROPERTY);     } } 
like image 76
Holger Avatar answered Sep 24 '22 01:09

Holger


Change your unsetAll method to setAll:

private void setAll(BigInteger numericParam, String stringParam, LocalDateTime dateParam) {     this.numericParam = numericParam;     this.stringParam = stringParam;     this.dateParam = dateParam; } 

Then invoke from your public setters like:

public void setNumericParam(BigInteger numericParam) {     setAll(Objects.requireNonNull(numericParam), null, null); } 

Note that Objects.requireNonNull is evaluated before setAll, so if you were to pass in a null numericParam, this would fail without changing any internal state.

like image 37
Andy Turner Avatar answered Sep 23 '22 01:09

Andy Turner