Gson序列化一般忽略空字符串

kuarbcqp  于 2022-11-06  发布在  其他
关注(0)|答案(3)|浏览(217)

有没有一个通用的方法告诉Gson不要写空字符串?
我非常不喜欢必须实现一个TypeAdapter来处理每个字段,就像answer here所建议的那样。

dauxcl2d

dauxcl2d1#

有点。据我所知,Gson不允许你控制太多的对象字段,而唯一的控制我知道你可以有它@JsonAdapter。例如,

import com.google.gson.annotations.JsonAdapter;

final class Pack {

    @JsonAdapter(EmptyStringTypeAdapter.class)
    final String foo;

    @JsonAdapter(EmptyStringTypeAdapter.class)
    final String bar;

    private Pack(final String foo, final String bar) {
        this.foo = foo;
        this.bar = bar;
    }

    static Pack of(final String foo, final String bar) {
        return new Pack(foo, bar);
    }

    @Override
    public String toString() {
        return foo + " " + bar;
    }

}

尽管对某些人来说这看起来很乏味,但它让你完全控制你的数据传输对象,让你选择如何处理这个或那个字符串。示例类型适配器如下:

final class EmptyStringTypeAdapter
        extends TypeAdapter<String> {

    private EmptyStringTypeAdapter() {
    }

    @Override
    @SuppressWarnings("resource")
    public void write(final JsonWriter jsonWriter, @Nullable final String s)
            throws IOException {
        if ( s == null || s.isEmpty() ) {
            jsonWriter.nullValue();
        } else {
            jsonWriter.value(s);
        }
    }

    @Override
    @Nonnull
    @SuppressWarnings("EnumSwitchStatementWhichMissesCases")
    public String read(final JsonReader jsonReader)
            throws IOException {
        final JsonToken token = jsonReader.peek();
        switch ( token ) {
        case NULL:
            return "";
        case STRING:
            return jsonReader.nextString();
        default:
            throw new IllegalStateException("Unexpected token: " + token);
        }
    }

}

这里需要注意的是,它不能从空值恢复空字符串(不幸的是,您在这里遇到了不可逆的转换),所以您可能还想看看https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/PostConstructAdapterFactory.java,以便在读取时恢复@JsonAdapter(EmptyStringTypeAdapter.class)注解的字段。示例测试:

private static final Gson gson = new Gson();

private static final Type listOfStringType = new TypeToken<List<String>>() {
}.getType();

public static void main(final String... args) {
    // Single elements
    ImmutableList.of(Pack.of("", ""), Pack.of("foo", ""), Pack.of("", "bar"), Pack.of("foo", "bar"))
            .stream()
            .peek(pack -> System.out.println("Pack before: " + pack))
            .map(gson::toJson)
            .peek(json -> System.out.println("JSON: " + json))
            .map(json -> gson.fromJson(json, Pack.class))
            .peek(pack -> System.out.println("Pack after: " + pack))
            .forEach(pack -> System.out.println());
    // Multiple elements
    final List<String> stringsBefore = ImmutableList.of("", "foo", "bar");
    System.out.println(stringsBefore);
    final String stringsJson = gson.toJson(stringsBefore, listOfStringType);
    System.out.println(stringsJson);
    final List<String> stringsAfter = gson.fromJson(stringsJson, listOfStringType);
    System.out.println(stringsAfter);
}

输出量:
前 Package :
JSON格式:{}
在以下时间后打包:null null -- [!]不是“”'”
前 Package :喷火
JSON:{“foo”:“foo”}
在以下之后打包:foo null -- [!]不是foo“”
前 Package :棒
JSON:{“bar”:“bar”}
在以下之后打包:空栏-- [!]不是“”栏
前 Package :脚踏杆
JSON:{“foo”:“foo”,“bar”:“bar”}
Package 后:foo bar
[,喷,巴]
["",“foo”,“bar”]
[,喷,巴]
然而,我不认为编写复杂的(反)序列化策略是一个好的选择,您可能对重新设计DTO和数据(反)序列化感兴趣。此外,""是 * 一个值 *,而null不是--我永远不会混合它们,我会修改为什么您的系统是这样设计的(它看起来确实像一个空/空值混合问题)。

e37o9pze

e37o9pze2#

感谢@Lyubomyr的回答,但我发现了一个更适合我们用例的解决方案:
如果我们将所有空字符串和对象设置为null,那么序列化后剩下的JSON只包含具有实际数据的节点:

/**
   * convert object to json
   */
  public String toJson(Object obj) {
    // Convert emtpy string and objects to null so we don't serialze them
    setEmtpyStringsAndObjectsToNull(obj);
    return gson.toJson(obj);
  }

  /**
   * Sets all empty strings and objects (all fields null) including sets to null.
   *
   * @param obj any object
   */
  public void setEmtpyStringsAndObjectsToNull(Object obj) {
    for (Field field : obj.getClass().getDeclaredFields()) {
      field.setAccessible(true);
      try {
        Object fieldObj = field.get(obj);
        if (fieldObj != null) {
          Class fieldType = field.getType();
          if (fieldType.isAssignableFrom(String.class)) {
            if(fieldObj.equals("")) {
              field.set(obj, null);
            }
          } else if (fieldType.isAssignableFrom(Set.class)) {
            for (Object item : (Set) fieldObj) {
              setEmtpyStringsAndObjectsToNull(item);
            }
            boolean setFielToNull = true;
            for (Object item : (Set) field.get(obj)) {
              if(item != null) {
                setFielToNull = false;
                break;
              }
            }
            if(setFielToNull) {
              setFieldToNull(obj, field);
            }
          } else if (!isPrimitiveOrWrapper(fieldType)) {
            setEmtpyStringsAndObjectsToNull(fieldObj);
            boolean setFielToNull = true;
            for (Field f : fieldObj.getClass().getDeclaredFields()) {
              f.setAccessible(true);
              if(f.get(fieldObj) != null) {
                setFielToNull = false;
                break;
              }
            }
            if(setFielToNull) {
              setFieldToNull(obj, field);
            }
          }
        }
      } catch (IllegalAccessException e) {
        System.err.println("Error while setting empty string or object to null: " + e.getMessage());
      }
    }
  }

  private void setFieldToNull(Object obj, Field field) throws IllegalAccessException {
    if(!Modifier.isFinal(field.getModifiers())) {
      field.set(obj, null);
    }
  }

  private boolean isPrimitiveOrWrapper(Class fieldType)  {
    return fieldType.isPrimitive()
        || fieldType.isAssignableFrom(Integer.class)
        || fieldType.isAssignableFrom(Boolean.class)
        || fieldType.isAssignableFrom(Byte.class)
        || fieldType.isAssignableFrom(Character.class)
        || fieldType.isAssignableFrom(Float.class)
        || fieldType.isAssignableFrom(Long.class)
        || fieldType.isAssignableFrom(Double.class)
        || fieldType.isAssignableFrom(Short.class);
  }

如果你有很多空字段,这可以在序列化和发送/写入数据库时节省时间(和空间)。

zyfwsgd6

zyfwsgd63#

由于您希望在序列化过程中省略空字符串,因此只能实现 JsonSerializer,而不能实现完整的 TypeAdapter

public class EmptyStringSerializer implements JsonSerializer<String> {
    @Override 
    public JsonElement serialize(String src, Type typeOfSrc, JsonSerializationContext context) {
        if (src == null || src.isEmpty())
            return JsonNull.INSTANCE;
        return new JsonPrimitive(src);
    }
}

然后将其作为 TypeAdapter 使用:

Gson gson = new GsonBuilder()
                .registerTypeAdapter(String.class, new EmptyStringSerializer())
                .create();

(This方法依赖于从序列化中省略空值。)

相关问题