java 中是否有函数来检查任何 json 属性是否为空?

Is there a function in java to check if any json attribute is null?

我有一条 JSON 格式的消息,我将其转换为 JSONObject,并且我有大约 30 个必填字段,我必须检查它们是否为空。如果这些必填字段之一为空,我将丢弃该消息,但是其他字段可以为空而无需丢弃该消息。 有什么有效的方法可以做到这一点而无需遍历每个字段并使用 isNull() 吗?

此外,JSON 对象是嵌套的,所以一个简单的 anyNull() 函数将不起作用,因为它只有 return 如果对象本身为 null 而不是变量本身为空。

我尝试使用 gson 将消息转换为 POJO,并为 10 个对象创建了 类

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

但由于许多 类 是嵌套的(其中之一是对象数组),因此使用简单的空检查器不起作用。

实际上,您的问题不是很清楚,因为您使用的“消息”一词指的是您的特定 class,但也可以更通用地指代 sent/received 消息。

内存中的 JSON 个元素类似:

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);
}

或JSON 流中的文档:

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()));
    }

}

他们两个都会投掷 null。但您似乎也想验证 Message 个实例:

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.

所以这说明了为什么上述空值检查不符合您的需要。您正在寻找的是 JSR-303。它不会像您希望的那样高效(消息实例被反序列化,验证也需要时间和资源),但从编码的角度来看它可能是高效的:

final Set<ConstraintViolation<V>> violations = validator.validate(message);
if ( !violations.isEmpty() ) {
    throw new ConstraintViolationException(violations);
}

甚至将其直接集成到 Gson 中,以便它为中间件提供服务:

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 {
}

和测试(为简洁起见使用 Lombok):

@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());
    }

}

最后,可能是最有效的(就读取 JSON 流而言),但与 JSR-303 相比非常有限(并且 NOT 在 Gson 中工作,因为Gson 不会将空检查传播到下游(反)序列化器),可以用类似的“功能”注释替换 @NotNull 的方式:

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());
    }

}

第三个,JSR-303,看起来最适合你。