如何在没有注释的情况下从序列化中排除特定的字段

我在努力学习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,并为字段名如firstNamecountry添加一个排除策略,但我似乎无法排除某些字段如country.name的属性。

使用方法public boolean shouldSkipField(FieldAttributes fa), FieldAttributes不包含足够的信息来匹配像country.name这样的过滤器。

附注:我想避免使用注释,因为我想改进这一点,并使用RegEx过滤字段。

编辑:我正在尝试看看是否有可能模拟Struts2 JSON插件的行为

使用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排除在外。 所以类型是相同的它是我想要精确定位并排除的对象图中的实际位置

366339 次浏览

所以,你想要排除 firstNamecountry.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.firstNameCountry.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}}

我假设country对象在student类中使用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 }}

一般来说,任何你不想序列化的字段都应该使用“transient”修饰符,这也适用于json序列化器(至少对我使用过的一些json序列化器是这样,包括gson)。

如果你不想name出现在序列化的json中,给它一个transient关键字,例如:

private transient String name;

更多详细信息在Gson文档中

Nishant提供了一个很好的解决方案,但还有更简单的方法。简单地用@Expose注释标记所需的字段,例如:

@Expose private Long id;

省略您不想序列化的任何字段。然后按如下方式创建你的Gson对象:

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

我提出了一个类工厂来支持这个功能。传入想要排除的字段或类的任何组合。

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);
}

或者可以说什么领域不会暴露:

Gson gson = gsonBuilder.excludeFieldsWithModifiers(Modifier.TRANSIENT).create();

在你的类上的属性:

private **transient** boolean nameAttribute;

我遇到了这个问题,其中我有一小部分字段只想从序列化中排除,所以我开发了一个相当简单的解决方案,使用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测试。

在阅读了所有可用的答案后,我发现,在我的情况下,最灵活的是使用自定义@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();

另一种方法(当您需要在运行时决定是否排除某个字段时尤其有用)是向您的gson实例注册TypeAdapter。在下面的例子:

Gson gson = new GsonBuilder()
.registerTypeAdapter(BloodPressurePost.class, new BloodPressurePostSerializer())

在下面的例子中,服务器将期望两个值中的一个,但由于它们都是int型,因此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;
}
}
我用了这个策略: 我排除了所有带有@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":"我会看到这个"}

我用自定义注释解决了这个问题。 这是我的“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;
}

我只是把@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)

Kotlin的@Transientannotation显然也能做到这一点。

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对象不将空值字段序列化为JSON。如果Java对象中的字段为空,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);
}

在kotlin中可以使用@Transient忽略字段…如。

data class MyClass{
@Transient var  myVar: Boolean
//....
}