Actually speaking your question is not very clear because you're using a word of "message" that refers your particular class, but can also be more generic referring sent/received messages.
So something like for JSON elements in memory:
public static void failOnNullRecursively(final JsonElement jsonElement) {
if ( jsonElement.isJsonNull() ) {
throw new IllegalArgumentException("null!");
}
if ( jsonElement.isJsonPrimitive() ) {
return;
}
if ( jsonElement.isJsonArray() ) {
for ( final JsonElement element : jsonElement.getAsJsonArray() ) {
failOnNullRecursively(element);
}
return;
}
if ( jsonElement.isJsonObject() ) {
for ( final Map.Entry<String, JsonElement> e : jsonElement.getAsJsonObject().entrySet() ) {
failOnNullRecursively(e.getValue());
}
return;
}
throw new AssertionError(jsonElement);
}
or JSON documents in streams:
public final class FailOnNullJsonReader
extends JsonReader {
private FailOnNullJsonReader(final Reader reader) {
super(reader);
}
public static JsonReader create(final Reader reader) {
return new FailOnNullJsonReader(reader);
}
@Override
public void nextNull() {
throw new IllegalStateException(String.format("null at %@!", getPath()));
}
}
Both of them will throw on null. But it also seems that you want to validate Message instances:
If one of these mandatory fields are null, I will discard the message, however other fields can be null without needing to discard the message.
So this tells why the above null-checks won't fit your needs. What you're looking for is JSR-303. It won't be that efficient as you might want to want it to be (message instances are deserialized, validation takes time and resources too), but it might be efficient from the coding perspective:
final Set<ConstraintViolation<V>> violations = validator.validate(message);
if ( !violations.isEmpty() ) {
throw new ConstraintViolationException(violations);
}
or even integrate it right into Gson so that it serves middleware:
public final class PostReadTypeAdapterFactory<V>
implements TypeAdapterFactory {
private final Predicate<? super TypeToken<?>> supports;
private final BiConsumer<? super TypeToken<V>, ? super V> onRead;
private PostReadTypeAdapterFactory(final Predicate<? super TypeToken<?>> supports, final BiConsumer<? super TypeToken<V>, ? super V> onRead) {
this.supports = supports;
this.onRead = onRead;
}
public static <V> TypeAdapterFactory create(final Predicate<? super TypeToken<?>> supports, final BiConsumer<? super TypeToken<V>, ? super V> onRead) {
return new PostReadTypeAdapterFactory<>(supports, onRead);
}
@Override
@Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !supports.test(typeToken) ) {
return null;
}
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, typeToken);
return new TypeAdapter<T>() {
@Override
public void write(final JsonWriter out, final T value)
throws IOException {
delegate.write(out, value);
}
@Override
public T read(final JsonReader in)
throws IOException {
final T readValue = delegate.read(in);
@SuppressWarnings("unchecked")
final V value = (V) readValue;
@SuppressWarnings("unchecked")
final TypeToken<V> valueTypeToken = (TypeToken<V>) typeToken;
onRead.accept(valueTypeToken, value);
return readValue;
}
};
}
}
public final class Jsr303Support {
private Jsr303Support() {
}
public static <V> TypeAdapterFactory createTypeAdapterFactory(final Validator validator) {
return PostReadTypeAdapterFactory.<V>create(
typeToken -> typeToken.getRawType().isAnnotationPresent(Validate.class),
(typeToken, value) -> {
final Set<ConstraintViolation<V>> violations = validator.validate(value);
if ( !violations.isEmpty() ) {
throw new ConstraintViolationException(violations);
}
}
);
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Validate {
}
And the test (using Lombok for brevity):
@Validate
@AllArgsConstructor
@EqualsAndHashCode
@ToString
final class Message {
@NotNull
final String foo;
@NotNull
final String bar;
@NotNull
final String baz;
}
public final class Jsr303SupportTest {
private static final Validator validator;
static {
try ( final ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory() ) {
validator = validatorFactory.getValidator();
}
}
public static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.disableInnerClassSerialization()
.registerTypeAdapterFactory(Jsr303Support.createTypeAdapterFactory(validator))
.create();
@Test
public void test() {
Assertions.assertEquals(new Message("1", "2", "3"), gson.fromJson("{\"foo\":\"1\",\"bar\":\"2\",\"baz\":\"3\"}", Message.class));
final ConstraintViolationException ex = Assertions.assertThrows(ConstraintViolationException.class, () -> gson.fromJson("{\"foo\":\"1\",\"bar\":null,\"baz\":\"3\"}", Message.class));
Assertions.assertEquals(1, ex.getConstraintViolations().size());
}
}
And finally, probably the most efficient (in terms of reading JSON stream), but very limited whencompared to JSR-303 (and NOT working in Gson because Gson does not propagate null-checking to downstream (de)serializers), way that could replace @NotNull with a similar "functional" annotation:
public final class NotNullTypeAdapterFactory
implements TypeAdapterFactory {
// note no external access
private NotNullTypeAdapterFactory() {
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final TypeAdapter<T> delegate = gson.getAdapter(typeToken);
return new TypeAdapter<T>() {
@Override
public void write(final JsonWriter out, @Nullable final T value)
throws IOException {
if ( value == null ) {
throw new IllegalArgumentException(typeToken + " with null");
}
delegate.write(out, value);
}
@Override
public T read(final JsonReader in)
throws IOException {
@Nullable
final T value = delegate.read(in);
if ( value == null ) {
throw new IllegalArgumentException(typeToken + " with null at " + in.getPath());
}
return value;
}
};
}
}
@AllArgsConstructor
@EqualsAndHashCode
@ToString
final class Message {
@JsonAdapter(NotNullTypeAdapterFactory.class)
final String foo;
@JsonAdapter(NotNullTypeAdapterFactory.class)
final String bar;
@JsonAdapter(NotNullTypeAdapterFactory.class)
final String baz;
}
public final class NotNullTypeAdapterFactoryTest {
public static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.disableInnerClassSerialization()
.create();
@Test
public void test() {
Assertions.assertEquals(new Message("1", "2", "3"), gson.fromJson("{\"foo\":\"1\",\"bar\":\"2\",\"baz\":\"3\"}", Message.class));
final IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, () -> gson.fromJson("{\"foo\":\"1\",\"bar\":null,\"baz\":\"3\"}", Message.class));
Assertions.assertEquals("whatever here, the above does not work anyway", ex.getMessage());
}
}
The third, JSR-303, looks like the best for you.