D'après ce que j'ai compris, AutoValue et AutoValue : Gson Extension fonctionnent, vous ne pouvez pas désérialiser Map<String, List<Obj>>
à partir du JSON donné en utilisant uniquement ces outils, puisqu'ils ne sont que de simples générateurs de code source. Ce dernier indique même qui crée un simple Gson TypeAdapterFactory pour chaque objet annoté AutoValue .
Prend le JSON donné et Map<String, List<Obj>>
vous pouvez le faire :
- ... ont
Map<String, Wrapper>
où la classe enveloppante contient List<Obj>
(et déclarer un adaptateur de type pour chaque @AutoValue
-annotée), ce qui permet d'avoir un objet médiateur intermédiaire muet nommé Wrapper
.
- ... mettre en œuvre un adaptateur de type personnalisé qui ne peut pas être généré par AutoValue : Gson Extension.
La deuxième option n'est pas aussi difficile qu'il n'y paraît et peut aider à contourner les cas où l'extension AutoValue ne peut pas générer d'adaptateurs de type pour d'autres cas sophistiqués.
Déclarer les jetons de type nécessaires
final class TypeTokens {
private TypeTokens() {
}
static final TypeToken<Map<String, List<Obj>>> mapStringToObjListTypeToken = new TypeToken<Map<String, List<Obj>>>() {
};
static final TypeToken<List<Obj>> objListTypeTypeToken = new TypeToken<List<Obj>>() {
};
}
Implémentation d'une fabrique d'adaptateurs de types personnalisés
Les usines d'adaptateurs de type Gson sont utilisées pour résoudre (et lier) un adaptateur de type particulier et, si nécessaire, lier l'adaptateur à des instances Gson pour gérer toute configuration Gson.
final class CustomTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory customTypeAdapterFactory = new CustomTypeAdapterFactory();
static TypeAdapterFactory getCustomTypeAdapterFactory() {
return customTypeAdapterFactory;
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( typeToken.getType().equals(mapStringToObjListTypeToken.getType()) ) {
@SuppressWarnings({ "unchecked", "rawtypes" })
final TypeAdapter<T> castTypeAdapter = (TypeAdapter) getMapStringToObjectListTypeAdapter(gson);
return castTypeAdapter;
}
return null;
}
}
Notez que la seule responsabilité de la fabrique est de renvoyer un adaptateur de type spécial qui peut ignorer les wrappers dans votre JSON. Si un autre type est demandé à la fabrique, le retour de null
est sûr et permet à Gson d'essayer de choisir le meilleur adaptateur d'un autre type (intégré ou de votre configuration).
Mettre en œuvre l'adaptateur de type
C'est en fait ce que fait Auto Value : Gson Extension ne semble pas pouvoir faire. Comme les adaptateurs de type sont essentiellement orientés vers le flux, ils peuvent sembler de trop bas niveau, mais c'est le mieux que Gson puisse faire parce que le flux est une technique très efficace, et il est également utilisé dans ce que Auto Value : Gson Extension génère.
final class MapStringToObjectListTypeAdapter
extends TypeAdapter<Map<String, List<Obj>>> {
private final TypeAdapter<List<Obj>> wrapperAdapter;
private MapStringToObjectListTypeAdapter(final TypeAdapter<List<Obj>> wrapperAdapter) {
this.wrapperAdapter = wrapperAdapter;
}
static TypeAdapter<Map<String, List<Obj>>> getMapStringToObjectListTypeAdapter(final Gson gson) {
return new MapStringToObjectListTypeAdapter(gson.getAdapter(objListTypeTypeToken));
}
@Override
@SuppressWarnings("resource")
public void write(final JsonWriter out, final Map<String, List<Obj>> value)
throws IOException {
if ( value == null ) {
// nulls must be written
out.nullValue();
} else {
out.beginObject();
for ( final Entry<String, List<Obj>> e : value.entrySet() ) {
out.name(e.getKey());
out.beginObject();
out.name("occupations");
wrapperAdapter.write(out, e.getValue());
out.endObject();
}
out.endObject();
}
}
@Override
public Map<String, List<Obj>> read(final JsonReader in)
throws IOException {
// if there's JSON null, then just return nothing
if ( in.peek() == NULL ) {
return null;
}
// or read the map
final Map<String, List<Obj>> result = new LinkedHashMap<>();
// expect the { token
in.beginObject();
// and read recursively until } is occurred
while ( in.peek() != END_OBJECT ) {
// this is the top-most level where varKey# occur
final String key = in.nextName();
in.beginObject();
while ( in.peek() != END_OBJECT ) {
final String wrapperName = in.nextName();
switch ( wrapperName ) {
case "occupations":
// if this is the "occupations" property, delegate the parsing to an underlying type adapter
result.put(key, wrapperAdapter.read(in));
break;
default:
// or just skip the value (or throw an exception, up to you)
in.skipValue();
break;
}
}
in.endObject();
}
in.endObject();
return result;
}
}
Auto Usine générée par la valeur
Contrairement à la fabrique d'adaptateurs de types personnalisés, certaines fabriques d'adaptateurs de types Gson sont déjà générées et peuvent traiter les éléments suivants abstract
classes comme ce que Obj
(au moins dans votre code source, pas dans celui qui est généré).
@GsonTypeAdapterFactory
abstract class GeneratedTypeAdapterFactory
implements TypeAdapterFactory {
public static TypeAdapterFactory getGeneratedTypeAdapterFactory() {
return new AutoValueGson_GeneratedTypeAdapterFactory();
}
}
Comment l'utiliser
private static void dump(final Map<?, ?> map) {
for ( final Entry<?, ?> e : map.entrySet() ) {
out.print(e.getKey());
out.print(" => ");
out.println(e.getValue());
}
}
...
final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(getGeneratedTypeAdapterFactory())
.registerTypeAdapterFactory(getCustomTypeAdapterFactory())
.create();
dump(gson.fromJson(json, mapStringToObjListTypeToken.getType()));
donne le résultat suivant :
varKey1 => [Obj{nom=nom1, valeur=val1}, Obj{nom=nom2, valeur=val2}]
varKey2 => [Obj{nom=nom1, valeur=val1}, Obj{nom=nom2, valeur=val2}]
Si vous n'utilisez que les wrappers de la première option, le résultat est le suivant :
varKey1 => Wrapper{occupations=[Obj{nom=nom1, valeur=val1}, Obj{nom=nom2, valeur=val2}]}
varKey2 => Wrapper{occupations=[Obj{nom=nom1, valeur=val1}, Obj{nom=nom2, valeur=val2}]}