Gson ignore @SerializedName

php_gutu

Mitglied
Hallo Zusammen

Ich habe ein Object, dass ich 1x als Json in eine Datei schreibe und 1x als DTO nutze. Nun verwende ich @SerializedName um die keys für das json file anders zu benennen. Beim DTO sollte @SerializedName ignoriert werden und der Name des Properties verwendet werden. Weiss jemand um Rat (ohne Reflection wenn möglich)?

Java:
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class App {
  public static void main(String[] args) {
    GsonBuilder builder = new GsonBuilder();
    builder.setPrettyPrinting();
    Gson gson = builder.create();
    Person person = new Person(1, "John", "Doe");
    String json = gson.toJson(person);

    System.out.println(json);
    /*
    Output 1 (output for json file):
    {
      "id": 1,
      "forename": "John",
      "surename": "Doe"
    }
    */

    Gson gson2 = new GsonBuilder().setExclusionStrategies(
        new ExclusionStrategy() {
          @Override
          public boolean shouldSkipField(FieldAttributes f) {
            return f.getAnnotation(ExcludeField.class) != null;
          }

          @Override
          public boolean shouldSkipClass(Class<?> clazz) {
            return false;
          }
        }
    ).setPrettyPrinting().create();
    String json2 = gson2.toJson(person); // toJson: how to ignore @SerializedName !

    System.out.println(json2);
    /*
    Output 2 (dto, without id, ignore @SerializedName and use property name):
    {
      "firstname": "John",
      "lastname": "Doe"
    }
    */
  }
}


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface ExcludeField {
}

class Person {
  @ExcludeField
  private int id;

  @SerializedName("forename")
  private String firstname;
  @SerializedName("surename")
  private String lastname;

  public Person(int id, String firstname, String lastname) {
    this.id = id;
    this.firstname = firstname;
    this.lastname = lastname;
  }
}
 

php_gutu

Mitglied
Ich hätte eine Lösung mit einem eigenen TypeAdapter. Der Lösungsansatz gefällt mir aber nicht ganz so gut.
Bsp.:
Java:
GsonBuilder builder2 = new GsonBuilder();
builder2.registerTypeAdapter(Person.class, new JsonSerializer<Person>() {
  @Override
  public JsonElement serialize(Person src, Type typeOfSrc, JsonSerializationContext context) {
    JsonObject obj = new JsonObject();
    obj.addProperty("firstname", src.getFirstname());
    obj.addProperty("lastname", src.getLastname());
    return obj;
  }
});
Gson gson2 = builder2.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY).setPrettyPrinting().create();
String json2 = gson2.toJson(person);
System.out.println(json2);

Hier ist es noch übersichtlich, aber wenn ich z.B. 15 Properties hätte, währe es mühsam. Klar ich lagere den Adapter aus, etc. Aber, es muss doch einen eleganteren Weg geben?
 

mrBrown

Super-Moderator
Mitarbeiter
Muss Person zu beiden Varianten serialisiert werden, oder wird es aus der einen deserialisiert und in die anderen serialisiert?
 

php_gutu

Mitglied
Ich habe eine sehr hässliche Variante mit einem TypeAdapter der mit Reflections arbeitet und bei allen properties die ein @SerializedName haben den originalen variablen Namen nimmt. Gefällt mir aber noch weniger.

Der Code

Java:
GsonBuilder builder2 = new GsonBuilder();
builder2.registerTypeAdapter(Person.class, new JsonSerializer<Person>() {
  @Override
  public JsonElement serialize(Person src, Type typeOfSrc, JsonSerializationContext context) {
    JsonObject obj = new JsonObject();

    for (Field field : Person.class.getDeclaredFields()) {
      String propertyName = field.getName();
      if (field.isAnnotationPresent(SerializedName.class)) {
        System.out.println(propertyName);

        for (Method method : Person.class.getDeclaredMethods()) {
          String methodName = "get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); // getter methods, wath is wit isMethod? Bad solution
          if (method.getName().equals(methodName)) {
            try {
              String value = "" + method.invoke(src, method.getParameters());
              obj.addProperty(propertyName, value);
            } catch (InvocationTargetException | IllegalAccessException e) {
              e.printStackTrace();
            }
          }
        }
      } else {
        Class clazz = null;
        switch (field.getType().toString()) {
          case "int":
            clazz = Integer.class;
            break;
          case "String":
            clazz = String.class;
            break;
          default:
            clazz = Object.class;
            break;
        }
        try {
          field.setAccessible(true);
          obj.addProperty(propertyName, String.valueOf(clazz.cast(field.get(src))));
          field.setAccessible(false);
        } catch (IllegalAccessException e) {
          e.printStackTrace();
        }
      }
    }

    return obj;
  }
});
Gson gson2 = builder2.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY).setPrettyPrinting().create();
String json2 = gson2.toJson(person);

System.out.println(json2);
 

mrBrown

Super-Moderator
Mitarbeiter
Kopierter und nur grob angepasster Code?


Kürzer und deutlich weniger umständlich wäre es z.B. so ;)
Java:
public JsonElement serialize(Person src, Type typeOfSrc, JsonSerializationContext context) {
  JsonObject obj = new JsonObject();
  try {
    for (Field field : Person.class.getDeclaredFields()) {
      String propertyName = field.getName();
      field.setAccessible(true);
      Object value = field.get(src);
      field.setAccessible(false);

      obj.add(propertyName, context.serialize(value));
    }
  } catch (IllegalAccessException e) {
    throw new RuntimeException(e);
  }
  return obj;
}

Theoretisch ginge das auch in Generisch für alle Typen, nicht nur für Person.
 

php_gutu

Mitglied
1000 Dank :)
Wenn man dies jetzt mit einer eigenen FieldNamingStrategy machen könnte währe es perfekt, da ich nicht auf Reflections zurückgreifen muss. Leider werden die Properties mit der serializedName Annotation nicht durchgelaufen:
Java:
FieldNamingStrategy customPolicy = new FieldNamingStrategy() {
  @Override
  public String translateName(Field f) {
    System.out.println("===> "  + f.getName());
    return f.getName();
  }
};
 

Ähnliche Java Themen

Neue Themen


Oben