我将 JSON
格式的消息转换为 JSONObject
,并且我有大约 30 个必填字段,我必须检查它们是否为空。如果这些必填字段之一为空,我将丢弃该消息,但其他字段可以为空而无需丢弃该消息。 有没有什么有效的方法可以做到这一点,而无需遍历每个字段并使用 isNull()
?
此外,JSON 对象是嵌套的,因此简单的 anyNull()
函数将不起作用,因为它只会在对象本身为 null 时返回,而在变量本身为 null 时不会返回。
我尝试使用 gson 将消息转换为 POJO,并为 10 个对象创建了类
Gson gson = new Gson();
Message message = gson.fromJson(msg, Message.class);
但是由于许多类是嵌套的(其中一个是对象数组),因此使用简单的空检查器不起作用。
答案 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 看起来最适合您。