如何让 Gson 使用访问器而不是字段?

问题描述 投票:0回答:3

默认情况下,Gson 使用字段作为序列化的基础。有没有办法让它改用访问器?

java json gson
3个回答
9
投票

Gson 的开发者他们从来没有被添加此功能的请求所动摇,他们担心隐藏 API 以添加对此的支持。

添加此功能的一种方法是使用 TypeAdapter(我对粗糙的代码表示歉意,但这演示了原理):

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import com.google.common.base.CaseFormat;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

public class AccessorBasedTypeAdaptor<T> extends TypeAdapter<T> {

  private Gson gson;

  public AccessorBasedTypeAdaptor(Gson gson) {
    this.gson = gson;
  }

  @SuppressWarnings("unchecked")
  @Override
  public void write(JsonWriter out, T value) throws IOException {
    out.beginObject();
    for (Method method : value.getClass().getMethods()) {
      boolean nonBooleanAccessor = method.getName().startsWith("get");
      boolean booleanAccessor = method.getName().startsWith("is");
      if ((nonBooleanAccessor || booleanAccessor) && !method.getName().equals("getClass") && method.getParameterTypes().length == 0) {
        try {
          String name = method.getName().substring(nonBooleanAccessor ? 3 : 2);
          name = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, name);
          Object returnValue = method.invoke(value);
          if(returnValue != null) {
            TypeToken<?> token = TypeToken.get(returnValue.getClass());
            TypeAdapter adapter = gson.getAdapter(token);
            out.name(name);
            adapter.write(out, returnValue);
          }
        } catch (Exception e) {
          throw new ConfigurationException("problem writing json: ", e);
        }
      }
    }
    out.endObject();
  }

  @Override
  public T read(JsonReader in) throws IOException {
    throw new UnsupportedOperationException("Only supports writes.");
  }
}

您可以将其注册为给定类型的普通类型适配器,或者通过 TypeAdapterfactory 注册 - 可能会检查运行时注释是否存在:

public class TypeFactory implements TypeAdapterFactory {

  @SuppressWarnings("unchecked")
  public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
    Class<? super T> t = type.getRawType();
    if(t.isAnnotationPresent(UseAccessor.class)) {
     return (TypeAdapter<T>) new AccessorBasedTypeAdaptor(gson);
    }
    return null;
  }

这可以在创建 gson 实例时正常指定:

new GsonBuilder().registerTypeAdapterFactory(new TypeFactory()).create();

2
投票

注意: 我是 EclipseLink JAXB (MOXy) 领导者和 JAXB (JSR-222) 专家组成员。

如果您无法让 Gson 执行您想要的操作,下面介绍了如何使用 MOXy 的本机 JSON 绑定来完成此操作。 MOXy 与任何 JAXB 实现一样,默认情况下将使用属性(公共)访问。您可以使用

@XmlAccessorType(XmlAccessType.FIELD)
配置字段访问。下面是一个例子:

客户

package forum11385214;

public class Customer {

    private String foo;
    private Address bar;

    public String getName() {
        return foo;
    }

    public void setName(String name) {
        this.foo = name;
    }

    public Address getAddress() {
        return bar;
    }

    public void setAddress(Address address) {
        this.bar = address;
    }

}

地址

package forum11385214;

public class Address {

    private String foo;

    public String getStreet() {
        return foo;
    }

    public void setStreet(String street) {
        this.foo = street;
    }

}

jaxb.properties

要将 MOXy 配置为 JAXB 提供程序,您需要在与域模型相同的包中添加一个名为

jaxb.properties
的文件,其中包含以下条目(请参阅:http://blog.bdoughan.com/2011/05/specifying- eclipselink-moxy-as-your.html).

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

演示

package forum11385214;

import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;

public class Demo {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String, Object>(2);
        properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
        properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
        JAXBContext jc = JAXBContext.newInstance(new Class[] {Customer.class}, properties);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        StreamSource json = new StreamSource("src/forum11385214/input.json");
        Customer customer = (Customer) unmarshaller.unmarshal(json, Customer.class).getValue();

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(customer, System.out);
    }

}

输入.json/输出

{
    "name" : "Jane Doe",
    "address" : {
        "street" : "1 Any Street"
    }
}

了解更多信息


0
投票

无论如何,我为@plasma147的answer实现了反序列化。

@Override
public T read(JsonReader in) throws IOException {
    try {
        T instance = type.getDeclaredConstructor().newInstance();
        in.beginObject();
        Map<String, Method> setters = new HashMap<>();
        for (Method method : type.getMethods()) {
            if (method.getName().startsWith("set") && method.getParameterTypes().length == 1) {
                String name = method.getName().substring(3);
                name = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, name);
                setters.put(name, method);
            }
        }
        while (in.hasNext()) {
            String name = in.nextName();
            Method setter = setters.get(name);
            if (setter != null) {
                Type paramType = setter.getGenericParameterTypes()[0];
                TypeAdapter<?> adapter = getTypeAdapter(paramType);
                Object value = adapter.read(in);

                try {
                    setter.invoke(instance, value);
                } catch (Exception e) {
                    throw new IOException("Problem setting value with setter: " + setter.getName(), e);
                }
            } else {
                in.skipValue();
            }
        }
        in.endObject();
        return instance;
    } catch (Exception e) {
        throw new IOException("Problem deserializing json", e);
    }
}

private TypeAdapter<?> getTypeAdapter(Type type) {
    if (type instanceof ParameterizedType) {
        ParameterizedType pType = (ParameterizedType) type;
        if (List.class.isAssignableFrom((Class<?>) pType.getRawType())) {
            Type elementType = pType.getActualTypeArguments()[0];
            return gson.getAdapter(TypeToken.getParameterized(List.class, elementType));
        }
    }
    return gson.getAdapter(TypeToken.get(type));
}

确保使用 @UseAccessor 注释您的数据对象,其定义如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface UseAccessor {
}
© www.soinside.com 2019 - 2024. All rights reserved.