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

时间:2021-03-07 07:55:28

标签: java json null gson

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

此外,JSON 对象是嵌套的,因此简单的 anyNull() 函数将不起作用,因为它只会在对象本身为 null 时返回,而在变量本身为 null 时不会返回。

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

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

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

1 个答案:

答案 0 :(得分:1)

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

类似于内存中的 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 实例:

<块引用>

如果这些必填字段之一为空,我将丢弃该消息,但其他字段可以为空而无需丢弃该消息。

所以这说明了为什么上述空检查不适合您的需求。您正在寻找的是 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 相比非常有限(并且 在 Gson 中工作,因为 Gson 不会将空检查传播到下游(de)serializers),可以用类似的“功能”注释替换 @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 看起来最适合您。

相关问题