我正在尝试学习 Gson,但我正在努力解决字段排除问题。这是我的课
public class Student {
private Long id;
private String firstName = "Philip";
private String middleName = "J.";
private String initials = "P.F";
private String lastName = "Fry";
private Country country;
private Country countryOfBirth;
}
public class Country {
private Long id;
private String name;
private Object other;
}
我可以使用 GsonBuilder 并为 firstName
或 country
之类的字段名称添加 ExclusionStrategy,但我似乎无法设法排除某些字段的属性,例如 country.name
。
使用方法 public boolean shouldSkipField(FieldAttributes fa)
,FieldAttributes 不包含足够的信息来将字段与 country.name
之类的过滤器相匹配。
PS:我想避免注释,因为我想对此进行改进并使用 RegEx 过滤字段。
编辑:我正在尝试看看是否可以模仿 Struts2 JSON plugin 的行为
使用 Gson
<interceptor-ref name="json">
<param name="enableSMD">true</param>
<param name="excludeProperties">
login.password,
studentList.*\.sin
</param>
</interceptor-ref>
编辑:我重新打开了这个问题,并添加了以下内容:
我添加了第二个具有相同类型的字段以进一步澄清这个问题。基本上我想排除 country.name
但不排除 countrOfBirth.name
。我也不想将 Country 排除为一种类型。所以类型是相同的,它是我想要查明和排除的对象图中的实际位置。
JsonSerializer
- 在您的情况下为 Country
- 然后应用 ExclusionStrategy
来决定要序列化的字段。
通常你不想序列化的任何字段都应该使用“transient”修饰符,这也适用于 json 序列化器(至少它适用于我使用过的一些,包括 gson)。
如果您不希望名称出现在序列化的 json 中,请给它一个临时关键字,例如:
private transient String name;
More details in the Gson documentation
Nishant 提供了一个很好的解决方案,但有一个更简单的方法。只需使用 @Expose 注释标记所需的字段,例如:
@Expose private Long id;
省略您不想序列化的任何字段。然后以这种方式创建您的 Gson 对象:
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
因此,您希望排除 firstName
和 country.name
。这是您的 ExclusionStrategy
的外观
public class TestExclStrat implements ExclusionStrategy {
public boolean shouldSkipClass(Class<?> arg0) {
return false;
}
public boolean shouldSkipField(FieldAttributes f) {
return (f.getDeclaringClass() == Student.class && f.getName().equals("firstName"))||
(f.getDeclaringClass() == Country.class && f.getName().equals("name"));
}
}
如果您仔细观察,它会为 Student.firstName
和 Country.name
返回 true
,这是您要排除的内容。
您需要像这样应用此ExclusionStrategy
,
Gson gson = new GsonBuilder()
.setExclusionStrategies(new TestExclStrat())
//.serializeNulls() <-- uncomment to serialize NULL fields as well
.create();
Student src = new Student();
String json = gson.toJson(src);
System.out.println(json);
这将返回:
{ "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91}}
我假设国家对象在学生类中用 id = 91L
初始化。
你可能会变得花哨。例如,您不想序列化名称中包含“name”字符串的任何字段。做这个:
public boolean shouldSkipField(FieldAttributes f) {
return f.getName().toLowerCase().contains("name");
}
这将返回:
{ "initials": "P.F", "country": { "id": 91 }}
编辑:根据要求添加了更多信息。
此 ExclusionStrategy
会执行此操作,但您需要传递“完全限定的字段名称”。见下文:
public class TestExclStrat implements ExclusionStrategy {
private Class<?> c;
private String fieldName;
public TestExclStrat(String fqfn) throws SecurityException, NoSuchFieldException, ClassNotFoundException
{
this.c = Class.forName(fqfn.substring(0, fqfn.lastIndexOf(".")));
this.fieldName = fqfn.substring(fqfn.lastIndexOf(".")+1);
}
public boolean shouldSkipClass(Class<?> arg0) {
return false;
}
public boolean shouldSkipField(FieldAttributes f) {
return (f.getDeclaringClass() == c && f.getName().equals(fieldName));
}
}
以下是我们如何通用地使用它。
Gson gson = new GsonBuilder()
.setExclusionStrategies(new TestExclStrat("in.naishe.test.Country.name"))
//.serializeNulls()
.create();
Student src = new Student();
String json = gson.toJson(src);
System.out.println(json);
它返回:
{ "firstName": "Philip" , "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91 }}
country.name
的字符串,并且仅在序列化字段 country
时排除字段 name
。它应该足够通用,以适用于每个具有 Country 类的名为 country 的属性的类。我不想为每个班级创建一个 ExclusionStrategy
在阅读了所有可用的答案后,我发现,就我而言,最灵活的是使用自定义 @Exclude
注释。因此,我为此实施了简单的策略(我不想使用 @Expose
标记所有字段,也不想使用与应用程序 Serializable
序列化冲突的 transient
):
注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {
}
战略:
public class AnnotationExclusionStrategy implements ExclusionStrategy {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getAnnotation(Exclude.class) != null;
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
用法:
new GsonBuilder().setExclusionStrategies(new AnnotationExclusionStrategy()).create();
addSerializationExclusionStrategy
或 addDeserializationExclusionStrategy
而不是 setExclusionStrategies
f.getAnnotation(Exclude.class) != null
更改为 f.getAnnotation(Exclude.class) == null
transient
时非常好。谢谢!
我遇到了这个问题,其中我只想从序列化中排除少量字段,因此我开发了一个相当简单的解决方案,该解决方案使用 Gson 的 @Expose
注释和自定义排除策略。
使用 @Expose
的唯一内置方法是设置 GsonBuilder.excludeFieldsWithoutExposeAnnotation()
,但顾名思义,没有显式 @Expose
的字段将被忽略。由于我只有几个要排除的字段,我发现将注释添加到每个字段的前景非常麻烦。
我实际上想要相反的情况,其中包含所有内容,除非我明确使用 @Expose
排除它。我使用以下排除策略来完成此操作:
new GsonBuilder()
.addSerializationExclusionStrategy(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
final Expose expose = fieldAttributes.getAnnotation(Expose.class);
return expose != null && !expose.serialize();
}
@Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
})
.addDeserializationExclusionStrategy(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
final Expose expose = fieldAttributes.getAnnotation(Expose.class);
return expose != null && !expose.deserialize();
}
@Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
})
.create();
现在我可以轻松地排除一些带有 @Expose(serialize = false)
或 @Expose(deserialize = false)
注释的字段(请注意,两个 @Expose
属性的默认值都是 true
)。您当然可以使用 @Expose(serialize = false, deserialize = false)
,但更简洁地通过声明字段 transient
来实现(在这些自定义排除策略中仍然有效)。
您可以使用 gson 探索 json 树。
尝试这样的事情:
gson.toJsonTree(student).getAsJsonObject()
.get("country").getAsJsonObject().remove("name");
您还可以添加一些属性:
gson.toJsonTree(student).getAsJsonObject().addProperty("isGoodStudent", false);
用 gson 2.2.4 测试。
我想出了一个类工厂来支持这个功能。传入要排除的字段或类的任意组合。
public class GsonFactory {
public static Gson build(final List<String> fieldExclusions, final List<Class<?>> classExclusions) {
GsonBuilder b = new GsonBuilder();
b.addSerializationExclusionStrategy(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return fieldExclusions == null ? false : fieldExclusions.contains(f.getName());
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return classExclusions == null ? false : classExclusions.contains(clazz);
}
});
return b.create();
}
}
要使用,请创建两个列表(每个列表都是可选的),然后创建您的 GSON 对象:
static {
List<String> fieldExclusions = new ArrayList<String>();
fieldExclusions.add("id");
fieldExclusions.add("provider");
fieldExclusions.add("products");
List<Class<?>> classExclusions = new ArrayList<Class<?>>();
classExclusions.add(Product.class);
GSON = GsonFactory.build(null, classExclusions);
}
private static final Gson GSON;
public String getSomeJson(){
List<Provider> list = getEntitiesFromDatabase();
return GSON.toJson(list);
}
我用自定义注释解决了这个问题。这是我的“SkipSerialisation”注释类:
@Target (ElementType.FIELD)
public @interface SkipSerialisation {
}
这是我的 GsonBuilder:
gsonBuilder.addSerializationExclusionStrategy(new ExclusionStrategy() {
@Override public boolean shouldSkipField (FieldAttributes f) {
return f.getAnnotation(SkipSerialisation.class) != null;
}
@Override public boolean shouldSkipClass (Class<?> clazz) {
return false;
}
});
例子 :
public class User implements Serializable {
public String firstName;
public String lastName;
@SkipSerialisation
public String email;
}
@Retention(RetentionPolicy.RUNTIME)
添加到您的注释中。
Kotlin 的@Transient
注解显然也起到了作用。
data class Json(
@field:SerializedName("serialized_field_1") val field1: String,
@field:SerializedName("serialized_field_2") val field2: String,
@Transient val field3: String
)
输出:
{"serialized_field_1":"VALUE1","serialized_field_2":"VALUE2"}
或者可以说什么字段不会暴露:
Gson gson = gsonBuilder.excludeFieldsWithModifiers(Modifier.TRANSIENT).create();
在你的属性类上:
private **transient** boolean nameAttribute;
excludeFieldsWithModifiers()
。
我使用了这个策略:我排除了每个没有用@SerializedName 注释标记的字段,即:
public class Dummy {
@SerializedName("VisibleValue")
final String visibleValue;
final String hiddenValue;
public Dummy(String visibleValue, String hiddenValue) {
this.visibleValue = visibleValue;
this.hiddenValue = hiddenValue;
}
}
public class SerializedNameOnlyStrategy implements ExclusionStrategy {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getAnnotation(SerializedName.class) == null;
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
Gson gson = new GsonBuilder()
.setExclusionStrategies(new SerializedNameOnlyStrategy())
.create();
Dummy dummy = new Dummy("I will see this","I will not see this");
String json = gson.toJson(dummy);
它返回
{"VisibleValue":"我会看到这个"}
另一种方法(如果您需要在运行时做出排除字段的决定特别有用)是在您的 gson 实例中注册一个 TypeAdapter。下面的例子:
Gson gson = new GsonBuilder()
.registerTypeAdapter(BloodPressurePost.class, new BloodPressurePostSerializer())
在下面的情况下,服务器会期望两个值之一,但由于它们都是整数,因此 gson 会将它们都序列化。我的目标是从发布到服务器的 json 中省略任何零(或更小)的值。
public class BloodPressurePostSerializer implements JsonSerializer<BloodPressurePost> {
@Override
public JsonElement serialize(BloodPressurePost src, Type typeOfSrc, JsonSerializationContext context) {
final JsonObject jsonObject = new JsonObject();
if (src.systolic > 0) {
jsonObject.addProperty("systolic", src.systolic);
}
if (src.diastolic > 0) {
jsonObject.addProperty("diastolic", src.diastolic);
}
jsonObject.addProperty("units", src.units);
return jsonObject;
}
}
我只是将 @Expose
注释放在这里,这是我使用的版本
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
在 Model
类中:
@Expose
int number;
public class AdapterRestApi {
在 Adapter
类中:
public EndPointsApi connectRestApi() {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(90000, TimeUnit.SECONDS)
.readTimeout(90000,TimeUnit.SECONDS).build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ConstantRestApi.ROOT_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
return retrofit.create (EndPointsApi.class);
}
我有 Kotlin 版本
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
internal annotation class JsonSkip
class SkipFieldsStrategy : ExclusionStrategy {
override fun shouldSkipClass(clazz: Class<*>): Boolean {
return false
}
override fun shouldSkipField(f: FieldAttributes): Boolean {
return f.getAnnotation(JsonSkip::class.java) != null
}
}
以及如何将其添加到 Retrofit GSONConverterFactory:
val gson = GsonBuilder()
.setExclusionStrategies(SkipFieldsStrategy())
//.serializeNulls()
//.setDateFormat(DateFormat.LONG)
//.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
//.setPrettyPrinting()
//.registerTypeAdapter(Id.class, IdTypeAdapter())
.create()
return GsonConverterFactory.create(gson)
这是我一直使用的:
Gson 中实现的默认行为是忽略空对象字段。
意味着 Gson 对象不会将具有空值的字段序列化为 JSON。如果 Java 对象中的字段为 null,Gson 将排除它。
您可以使用此函数将某些对象转换为 null 或由您自己设置
/**
* 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);
}
transient
而不是@Expose
的问题是,您仍然必须在客户端上模拟 POJO,其中包含可能进入的所有字段。对于可能在项目之间共享的后端 API,如果添加了其他字段,这可能会出现问题。本质上,它是字段的白名单与黑名单。