Ich versuche Gson zu lernen und kämpfe mit dem Feldausschluss. Hier sind meine Klassen
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;
}
Ich kann den GsonBuilder verwenden und eine ExclusionStrategy für einen Feldnamen wie firstName
oder country
hinzufügen, aber ich schaffe es nicht, Eigenschaften bestimmter Felder wie country.name
auszuschließen.
Bei Verwendung der Methode public boolean shouldSkipField(FieldAttributes fa)
enthält FieldAttributes nicht genügend Informationen, um das Feld mit einem Filter wie country.name
abzugleichen.
Ich würde mich über eine Hilfe bei der Lösung dieses Problems freuen.
PS: Ich möchte Anmerkungen vermeiden, da ich dies verbessern und RegEx verwenden möchte, um Felder herauszufiltern.
Vielen Dank
Edit: Ich versuche zu sehen, ob es möglich ist, das Verhalten von Struts2 JSON-Plugin zu emulieren
mit Gson
<interceptor-ref name="json">
<param name="enableSMD">true</param>
<param name="excludeProperties">
login.password,
studentList.*\.sin
</param>
</interceptor-ref>
Edit: Ich habe die Frage mit folgendem Zusatz erneut geöffnet:
Ich habe ein zweites Feld mit demselben Typ hinzugefügt, um dieses Problem weiter zu klären. Grundsätzlich möchte ich country.name
ausschließen, aber nicht countrOfBirth.name
. Ich möchte auch nicht Country als Typ ausschließen .. Die Typen sind also die gleichen, es ist die tatsächliche Stelle im Objektdiagramm, die ich genau festlegen und ausschließen möchte.
Für alle Felder, die Sie nicht serialisieren möchten, sollten Sie in der Regel den Modifikator "Transient" verwenden. Dies gilt auch für Json-Serialisierer (zumindest für einige wenige, die ich verwendet habe, einschließlich Gson).
Wenn Sie nicht möchten, dass der Name im serialisierten json angezeigt wird, geben Sie ihm ein vorübergehendes Schlüsselwort, z.
private transient String name;
Nishant bot eine gute Lösung, aber es gibt einen einfacheren Weg. Markieren Sie einfach die gewünschten Felder mit der @Expose-Annotation.
@Expose private Long id;
Lassen Sie alle Felder aus, die Sie nicht serialisieren möchten. Dann erstellen Sie einfach Ihr Gson-Objekt auf diese Weise:
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
Sie möchten also ausschließenfirstName
und country.name
. So sollte Ihre ExclusionStrategy
aussehen
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"));
}
}
Wenn Sie genau sehen, wird true
für Student.firstName
und Country.name
zurückgegeben, was Sie ausschließen möchten.
Sie müssen diese ExclusionStrategy
so anwenden,
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);
Dies gibt zurück:
{"middleName": "J.", "Initialen": "P.F", "lastName": "Fry", "country": {"id": 91}}
Ich gehe davon aus, dass das Länderobjekt in der Studentenklasse mit id = 91L
initialisiert wird.
Sie können Lust bekommen. Beispielsweise möchten Sie kein Feld serialisieren, dessen Name die Zeichenfolge "name" enthält. Mach das:
public boolean shouldSkipField(FieldAttributes f) {
return f.getName().toLowerCase().contains("name");
}
Dies wird zurückkehren:
{ "initials": "P.F", "country": { "id": 91 }}
EDIT: Je nach Wunsch weitere Informationen hinzugefügt.
Dieses ExclusionStrategy
erledigt das, aber Sie müssen "Fully Qualified Field Name" übergeben. Siehe unten:
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));
}
}
So können wir es generisch verwenden.
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);
Es gibt zurück:
{ "firstName": "Philip" , "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91 }}
Nachdem ich alle verfügbaren Antworten gelesen hatte, stellte ich fest, dass es am flexibelsten war, die benutzerdefinierte @Exclude
-Annotation zu verwenden. Also habe ich eine einfache Strategie dafür implementiert (ich wollte nicht alle Felder mit @Expose
markieren oder transient
verwenden, was in der app Serializable
-Serialisierung zu Konflikten führte):
Anmerkung:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {
}
Strategie:
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;
}
}
Verwendungszweck:
new GsonBuilder().setExclusionStrategies(new AnnotationExclusionStrategy()).create();
Ich bin auf dieses Problem gestoßen, in dem ich einige wenige Felder hatte, die ich nur von der Serialisierung ausschließen wollte. Daher entwickelte ich eine relativ einfache Lösung, die die @Expose
-Annotation von Gson mit benutzerdefinierten Ausschlussstrategien verwendet.
Die einzige integrierte Methode für die Verwendung von @Expose
ist die Einstellung von GsonBuilder.excludeFieldsWithoutExposeAnnotation()
. Felder, die keinen expliziten @Expose
enthalten, werden jedoch, wie der Name schon sagt, ignoriert. Da ich nur wenige Felder hatte, die ich ausschließen wollte, fand ich die Möglichkeit, die Anmerkung zu jedem Feld hinzuzufügen, sehr umständlich.
Ich wollte eigentlich die Umkehrung, in der alles enthalten war, es sei denn, ich habe ausdrücklich @Expose
verwendet, um es auszuschließen. Ich habe die folgenden Ausschlussstrategien verwendet, um dies zu erreichen:
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();
Nun kann ich einige Felder mit @Expose(serialize = false)
- oder @Expose(deserialize = false)
-Annotationen ausschließen (beachten Sie, dass der Standardwert für beide @Expose
-Attribute true
ist). Sie können natürlich @Expose(serialize = false, deserialize = false)
verwenden, dies wird jedoch präziser ausgeführt, indem Sie stattdessen das Feld transient
deklarieren (das bei diesen benutzerdefinierten Ausschlussstrategien immer noch wirksam ist).
Ich habe mir eine Klassenfabrik ausgedacht, um diese Funktionalität zu unterstützen. Übergeben Sie eine beliebige Kombination von Feldern oder Klassen, die Sie ausschließen möchten.
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();
}
}
Erstellen Sie zur Verwendung zwei Listen (jede ist optional) und erstellen Sie Ihr GSON-Objekt:
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);
}
Sie können den Json-Baum mit Gson erkunden.
Versuchen Sie etwas so:
gson.toJsonTree(student).getAsJsonObject()
.get("country").getAsJsonObject().remove("name");
Sie können auch einige Eigenschaften hinzufügen:
gson.toJsonTree(student).getAsJsonObject().addProperty("isGoodStudent", false);
Getestet mit gson 2.2.4.
Ich habe dieses Problem mit benutzerdefinierten Anmerkungen gelöst .. Dies ist meine Anmerkungsklasse "SkipSerialization":
@Target (ElementType.FIELD)
public @interface SkipSerialisation {
}
und das ist mein 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;
}
});
Beispiel:
public class User implements Serializable {
public String firstName;
public String lastName;
@SkipSerialisation
public String email;
}
Oder kann sagen, was Felder nicht ausstellen werden mit:
Gson gson = gsonBuilder.excludeFieldsWithModifiers(Modifier.TRANSIENT).create();
auf Ihre Klasse über Attribut:
private **transient** boolean nameAttribute;
Ich habe diese Strategie angewendet: Ich habe jedes Feld ausgeschlossen, das nicht mit @SerializedName Annotation markiert ist,
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);
Es kehrt zurück
{"VisibleValue": "Ich werde das sehen"}
Ein weiterer Ansatz (besonders nützlich, wenn Sie entscheiden müssen, ob ein Feld zur Laufzeit ausgeschlossen werden soll), ist die Registrierung eines TypeAdapter in Ihrer gson-Instanz. Beispiel unten:
Gson gson = new GsonBuilder()
.registerTypeAdapter(BloodPressurePost.class, new BloodPressurePostSerializer())
In dem folgenden Fall würde der Server einen von zwei Werten erwarten. Da sie jedoch beide Werte sind, werden sie beide von gson serialisiert. Mein Ziel war es, jeden Wert, der Null ist (oder weniger), aus dem auf dem Server geposteten Json auszulassen.
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;
}
}
Kotlins @Transient
annotation macht offensichtlich auch den Trick.
data class Json(
@field:SerializedName("serialized_field_1") val field1: String,
@field:SerializedName("serialized_field_2") val field2: String,
@Transient val field3: String
)
Ausgabe:
{"serialized_field_1":"VALUE1","serialized_field_2":"VALUE2"}
Ich arbeite nur mit der @Expose
-Anmerkung, hier meiner Version, die ich verwende
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
In der Variable Model
:
@Expose
int number;
public class AdapterRestApi {
In der Adapter
-Klasse:
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);
}
Das was ich immer benutze:
Das in Gson implementierte Standardverhalten ist, dass Nullobjektfelder ignoriert werden.
Bedeutet, dass das Gson-Objekt keine Felder mit Nullwerten für JSON serialisiert. Wenn ein Feld in einem Java-Objekt null ist, wird es von Gson ausgeschlossen.
Mit dieser Funktion können Sie ein Objekt in ein von Ihnen festgelegtes Null-Objekt konvertieren
/**
* 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);
}
Ich habe eine Kotlin-Version
@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
}
}
und wie Sie dies zu Retrofit GSONConverterFactory hinzufügen können:
val gson = GsonBuilder()
.setExclusionStrategies(SkipFieldsStrategy())
//.serializeNulls()
//.setDateFormat(DateFormat.LONG)
//.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
//.setPrettyPrinting()
//.registerTypeAdapter(Id.class, IdTypeAdapter())
.create()
return GsonConverterFactory.create(gson)