我们从前端发送JSON字符串作为java代码的输入。 Java端使用Gson将其转换为bean。现在,我的前端人员向我提出了这些要求:
我们提出了这个想法:
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.lang.reflect.Type;
import java.util.Objects;
import org.junit.Test;
import com.google.gson.*;
class ResetableValue<T> {
private static enum Content {
VALUE, RESET, NOT_PROVIDED
};
private final T value;
private final Content content;
public ResetableValue(T value) {
this(value, Content.VALUE);
}
private ResetableValue(T value, Content content) {
this.value = value;
this.content = content;
}
static <T> ResetableValue<T> asReset() {
return new ResetableValue<>(null, Content.RESET);
}
static <T> ResetableValue<T> asNotProvided() {
return new ResetableValue<>(null, Content.NOT_PROVIDED);
}
T getValue() {
if (content != Content.VALUE) {
throw new IllegalStateException("can't provide value for " + content);
}
return value;
}
boolean isReset() {
return content == Content.RESET;
}
boolean isNotProvided() {
return content == Content.NOT_PROVIDED;
}
@Override
public String toString() {
if (content == Content.VALUE) {
return Objects.toString(value);
}
return content.toString();
}
}
class ResetableValueDeserializer implements JsonDeserializer<ResetableValue<String>> {
public ResetableValue<String> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return new ResetableValue<String>(json.getAsJsonPrimitive().getAsString());
}
}
class ExampleBean {
private ResetableValue<String> property = ResetableValue.asNotProvided();
public ResetableValue<String> getProperty() {
if (property == null) {
return ResetableValue.asReset();
}
return property;
}
@Override
public String toString() {
return "property: " + Objects.toString(property);
}
}
public class GsonStuffTest {
Gson gson = new GsonBuilder().registerTypeAdapter(ResetableValue.class, new ResetableValueDeserializer()).create();
@Test
public void testValue() {
String serializedContent = "{\"property\":\"foo\"}";
ExampleBean bean = gson.fromJson(serializedContent, ExampleBean.class);
assertThat(bean.getProperty().getValue(), is("foo"));
}
@Test
public void testIsNotProvided() {
String serializedContent = "{}";
ExampleBean bean = gson.fromJson(serializedContent, ExampleBean.class);
assertThat(bean.getProperty().isNotProvided(), is(true));
}
@Test
public void testIsReset() {
String serializedContent = "{\"property\":null}";
ExampleBean bean = gson.fromJson(serializedContent, ExampleBean.class);
assertThat(bean.getProperty().isReset(), is(true));
}
}
请注意:这个想法当然是在bean中有多个不同类型的ResetableValue
字段。然后一个字段可能关心一个值,一个被省略,另一个被设置为null。
getProperty()
方法中处理“重置”的情况。这意味着:拥有自定义反序列化器是不够的,我还需要将特殊检查放入任何getter方法。那么:有更优雅的解决方案吗?有没有办法让Gson 区分“属性没有显示”与“属性设置为null”?我想用另一种方式来表达我的问题是:在使用Gson将JSON反序列化为bean时是否支持“Optionals”?
答案 0 :(得分:1)
上面的例子“有效” - 但我真的不喜欢我必须在我的bean的getProperty()方法中处理“重置”的情况。这意味着:拥有自定义反序列化器是不够的,我还需要将特殊检查放入任何getter方法。那么:有更优雅的解决方案吗?有没有办法让Gson区分“财产没有出现”与“财产设置为空”?
排序。您的getProperty
似乎有一个冗余检查:它应该永远不会检查null
并且在任何情况下只返回property
字段,假设Gson设法实例化它。
上述例子声称是通用的;但显然反序列化器代码仅适用于字符串属性。有没有办法让这个“真正通用”?
是的,通过类型适配器工厂和类型适配器(关于后者:JsonSerializer
和JsonDeserializer
类使用消耗更多内存的JSON树,但类型适配器是流式的并且消耗更少。)< / p>
让我们考虑你有一个通用的三态值持有者,如下所示。 我也会隐藏构造函数,使其更流畅,并封装它实例化(或未实例化)的方式。
final class Value<T> {
private static final Value<?> noValue = new Value<>(State.NO_VALUE, null);
private static final Value<?> undefined = new Value<>(State.UNDEFINED, null);
private enum State {
VALUE,
NO_VALUE,
UNDEFINED
}
private final State state;
private final T value;
private Value(final State state, final T value) {
this.value = value;
this.state = state;
}
static <T> Value<T> value(final T value) {
if ( value == null ) {
return noValue();
}
return new Value<>(State.VALUE, value);
}
static <T> Value<T> noValue() {
@SuppressWarnings("unchecked")
final Value<T> value = (Value<T>) noValue;
return value;
}
static <T> Value<T> undefined() {
@SuppressWarnings("unchecked")
final Value<T> value = (Value<T>) undefined;
return value;
}
T getValue()
throws IllegalStateException {
if ( state != State.VALUE ) {
throw new IllegalStateException("Can't provide value for " + state);
}
return value;
}
boolean isValue() {
return state == State.VALUE;
}
boolean isNoValue() {
return state == State.NO_VALUE;
}
boolean isUndefined() {
return state == State.UNDEFINED;
}
@Override
public String toString() {
if ( state != State.VALUE ) {
return state.toString();
}
return Objects.toString(value);
}
}
接下来,定义一个简单的数据包来保存值。
请注意,您必须将它们声明为undefined()
以保留空对象语义,或者为其指定null
,以便Gson在下面处理它(您选择)。
final class DataBag {
final Value<Integer> integer = null;/* = undefined()*/
final Value<String> string = null;/* = undefined()*/
private DataBag() {
}
}
这里有一些反射实用程序代码来分析类型参数化并构建子到超类层次结构迭代器(我不知道如何从头开始创建Java 8流):
final class Reflection {
private Reflection() {
}
static Type getTypeParameter0(final Type type) {
if ( !(type instanceof ParameterizedType) ) {
return Object.class;
}
final ParameterizedType parameterizedType = (ParameterizedType) type;
return parameterizedType.getActualTypeArguments()[0];
}
static Iterable<Class<?>> subToSuperClass(final Class<?> subClass) {
return subToSuperClass(Object.class, subClass);
}
static <SUP, SUB extends SUP> Iterable<Class<?>> subToSuperClass(final Class<SUP> superClass, final Class<SUB> subClass) {
if ( !superClass.isAssignableFrom(subClass) ) {
throw new IllegalArgumentException(superClass + " is not assignable from " + subClass);
}
return () -> new Iterator<Class<?>>() {
private Class<?> current = subClass;
@Override
public boolean hasNext() {
return current != null;
}
@Override
public Class<?> next() {
if ( current == null ) {
throw new NoSuchElementException();
}
final Class<?> result = current;
current = result != superClass ? current.getSuperclass() : null;
return result;
}
};
}
}
ValueTypeAdapterFactory
通过将(de)序列化过程委托给下游类型适配器来负责任何通用值。
final class ValueTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory valueTypeAdapterFactory = new ValueTypeAdapterFactory();
private ValueTypeAdapterFactory() {
}
static TypeAdapterFactory getValueTypeAdapterFactory() {
return valueTypeAdapterFactory;
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !Value.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
final Type valueTypeParameter = getTypeParameter0(typeToken.getType());
// Some boring Java unchecked stuff here...
@SuppressWarnings("unchecked")
final TypeAdapter<Object> innerTypeAdapter = (TypeAdapter<Object>) gson.getDelegateAdapter(this, TypeToken.get(valueTypeParameter));
final TypeAdapter<Value<Object>> outerTypeAdapter = new ValueTypeAdapter<>(innerTypeAdapter);
@SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) outerTypeAdapter;
return typeAdapter;
}
private static final class ValueTypeAdapter<T>
extends TypeAdapter<Value<T>> {
private final TypeAdapter<T> innerTypeAdapter;
private ValueTypeAdapter(final TypeAdapter<T> innerTypeAdapter) {
this.innerTypeAdapter = innerTypeAdapter;
}
@Override
public void write(final JsonWriter out, final Value<T> value)
throws IOException {
if ( value.isValue() ) {
final T innerValue = value.getValue();
innerTypeAdapter.write(out, innerValue);
return;
}
// Considering no-value is undefined in order not to produce illegal JSON documents (dangling property names, etc)
if ( value.isNoValue() || value.isUndefined() ) {
innerTypeAdapter.write(out, null);
return;
}
throw new AssertionError();
}
@Override
public Value<T> read(final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
if ( token == NULL ) {
in.nextNull();
return noValue();
}
return value(innerTypeAdapter.read(in));
}
}
}
PostValueTypeAdapterFactory
负责通过使用反射“调整”具有null
- 初始化Value
字段的POJO。
如果不注册此工厂,则必须手动使用Value
初始化所有undefined()
字段。
为简单起见,此处未实现任何顺序数据结构,如iterables / collections / lists | sets,maps或arrays。
final class PostValueTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory postValueTypeAdapterFactory = new PostValueTypeAdapterFactory();
private PostValueTypeAdapterFactory() {
}
static TypeAdapterFactory getPostValueTypeAdapterFactory() {
return postValueTypeAdapterFactory;
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final List<Field> valueFields = collectValueFields(typeToken.getRawType());
if ( valueFields.isEmpty() ) {
return null;
}
final TypeAdapter<T> delegateTypeAdapter = gson.getDelegateAdapter(this, typeToken);
return new PostValueTypeAdapter<>(delegateTypeAdapter, valueFields);
}
// Just scan class the whole type hierarchy (except java.lang.Object) to find any occurrences of Value<T> fields
private static List<Field> collectValueFields(final Class<?> type) {
return StreamSupport.stream(subToSuperClass(type).spliterator(), false)
.filter(clazz -> clazz != Object.class)
.flatMap(clazz -> Stream.of(clazz.getDeclaredFields()))
.filter(field -> field.getType() == Value.class)
.peek(field -> field.setAccessible(true))
.collect(toImmutableList());
}
private static final class PostValueTypeAdapter<T>
extends TypeAdapter<T> {
private final TypeAdapter<T> delegateTypeAdapter;
private final List<Field> valueFields;
private PostValueTypeAdapter(final TypeAdapter<T> delegateTypeAdapter, final List<Field> valueFields) {
this.delegateTypeAdapter = delegateTypeAdapter;
this.valueFields = valueFields;
}
@Override
public void write(final JsonWriter out, final T value)
throws IOException {
delegateTypeAdapter.write(out, value);
}
@Override
public T read(final JsonReader in)
throws IOException {
try {
final T value = delegateTypeAdapter.read(in);
for ( final Field valueField : valueFields ) {
// A Value<T> field is null? Make it undefined
if ( valueField.get(value) == null ) {
valueField.set(value, undefined());
}
}
return value;
} catch ( final IllegalAccessException ex ) {
throw new IOException(ex);
}
}
}
}
JUnit测试:
public final class GsonStuffTest {
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(getValueTypeAdapterFactory())
.registerTypeAdapterFactory(getPostValueTypeAdapterFactory())
.create();
@Test
public void testIsValue() {
final DataBag dataBag = parseDataBag("{\"integer\":100,\"string\":\"foo\"}");
assertThat(dataBag.integer.isValue(), is(true));
assertThat(dataBag.integer.getValue(), is(100));
assertThat(dataBag.string.isValue(), is(true));
assertThat(dataBag.string.getValue(), is("foo"));
}
@Test
public void testIsNoValue() {
final DataBag dataBag = parseDataBag("{\"integer\":null,\"string\":null}");
assertThat(dataBag.integer.isNoValue(), is(true));
assertThat(dataBag.string.isNoValue(), is(true));
}
@Test
public void testIsUndefined() {
final DataBag dataBag = parseDataBag("{}");
assertThat(dataBag.integer.isUndefined(), is(true));
assertThat(dataBag.string.isUndefined(), is(true));
}
private static DataBag parseDataBag(final String json) {
return gson.fromJson(json, DataBag.class);
}
}