public class Data {
@NonNull
List
}
String json = "{\"strings\":null}";
Data data = new Gson().fromJson(json, new TypeToken(){}.getType());
System.out.println(data.strings);
返回结果是:null,我期望返回:[]。
Why do you think that Gson should keep the initialization values if you set strings to null explicitly?
Gson works as expected. Note that field initializers _do not exist per se_ and they are simply copied to class constructors by javac. Moreover, you cannot access field initializers in runtime just because of that. Gson only uses the default constructor only if it exists.
You can work around it though:
final class NonnullTypeAdapterFactory
implements TypeAdapterFactory {
private final Function<? super Field, ? extends Supplier<?>> getDefaultFieldValue;
private NonnullTypeAdapterFactory(final Function<? super Field, ? extends Supplier<?>> getDefaultFieldValue) {
this.getDefaultFieldValue = getDefaultFieldValue;
}
static TypeAdapterFactory get(final Function<? super Field, ? extends Supplier<?>> getDefaultFieldValue) {
return new NonnullTypeAdapterFactory(getDefaultFieldValue);
}
@Override
@Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
@SuppressWarnings("unchecked")
final TypeAdapter<T> delegateTypeAdapter = (TypeAdapter<T>) gson.getDelegateAdapter((TypeAdapterFactory) this, (TypeToken<?>) typeToken);
if ( !(delegateTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter) ) {
return null;
}
return NonnullTypeAdapter.of(delegateTypeAdapter, typeToken, getDefaultFieldValue);
}
private static final class NonnullTypeAdapter<T>
extends TypeAdapter<T> {
private final TypeAdapter<T> typeAdapter;
private final Map<Field, Supplier<?>> nonnullFields;
private NonnullTypeAdapter(final TypeAdapter<T> typeAdapter, final Map<Field, Supplier<?>> nonnullFields) {
this.typeAdapter = typeAdapter;
this.nonnullFields = nonnullFields;
}
public static <T> NonnullTypeAdapter<T> of(final TypeAdapter<T> typeAdapter, final TypeToken<T> typeToken,
final Function<? super Field, ? extends Supplier<?>> getDefaultFieldValue) {
final List<Class<?>> hierarchyClasses = getHierarchyClasses(typeToken.getRawType());
final Map<Field, Supplier<?>> nonnullFields = hierarchyClasses.stream()
.flatMap(clazz -> Stream.of(clazz.getDeclaredFields()))
.filter(field -> field.getAnnotation(Nonnull.class) != null)
.peek(field -> field.setAccessible(true))
.collect(Collectors.collectingAndThen(Collectors.toMap(Function.identity(), getDefaultFieldValue), Collections::unmodifiableMap));
return new NonnullTypeAdapter<>(typeAdapter, nonnullFields);
}
@Override
public void write(final JsonWriter out, final T value)
throws IOException {
typeAdapter.write(out, value);
}
@Override
public T read(final JsonReader in)
throws IOException {
try {
final T object = typeAdapter.read(in);
for ( final Map.Entry<Field, Supplier<?>> entry : nonnullFields.entrySet() ) {
final Field field = entry.getKey();
final Supplier<?> defaultValue = entry.getValue();
field.set(object, defaultValue.get());
}
return object;
} catch ( final IllegalArgumentException | IllegalAccessException ex ) {
throw new RuntimeException(ex);
}
}
}
private static List<Class<?>> getHierarchyClasses(final Class<?> clazz) {
final List<Class<?>> classes = new ArrayList<>();
for ( Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass() ) {
classes.add(c);
}
return Collections.unmodifiableList(classes);
}
}
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(NonnullTypeAdapterFactory.get(field -> {
final Class<?> type = field.getType();
if ( List.class.isAssignableFrom(type) ) {
return ArrayList::new;
}
return () -> null;
}))
.create();
public static void main(final String... args) {
final String json = "{\"strings\":null}";
final Data data = gson.fromJson(json, Data.class);
System.out.println(data.strings);
}
will produce:
[]
Again, not a bug.
Most helpful comment
Why do you think that Gson should keep the initialization values if you set
stringstonullexplicitly?Gson works as expected. Note that field initializers _do not exist per se_ and they are simply copied to class constructors by
javac. Moreover, you cannot access field initializers in runtime just because of that. Gson only uses the default constructor only if it exists.You can work around it though:
will produce:
Again, not a bug.