-1

I have a message in JSON format that I converted to a JSONObject, and I have around 30 mandatory fields that I have to check for whether they're null or not. 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. Is there any efficient way I can do this without going through each and every field and using isNull() ?

Also, the JSON objects are nested, so a simple anyNull() function would not work since it would only return if the object itself is null and not if the variables themselves are null.

I tried using gson to convert the message to a POJO, and created classes for 10 objects

Gson gson = new Gson();
Message message = gson.fromJson(msg, Message.class);

but since many classes are nested (and one of which is an array of objects) using simple null checkers don't work.

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.