2 votes

Comment sérialiser et désérialiser des objets contenant des HashMaps et des Pairs à l'aide de GSON

J'ai cette HashMap Map,Map> pathData = new HashMap<>(); comme attribut d'un autre objet (Tour_Object) et j'essaie de le sérialiser/désérialiser en json en utilisant la bibliothèque GSON.

public static String setTourToJson(Tour_Object tourObject)
{
    Gson gson = new Gson();
    return gson.toJson(tourObject);
}

public static Tour_Object getTourFromJson(String JsonString)
{
    Gson gson = new Gson();
    return gson.fromJson(JsonString, new TypeToken() {
    }.getType());
}

L'exception suivante est lancée lorsque je procède à la désérialisation:

04-19 11:18:49.449 29076-29076/abff.fxguide E/AndroidRuntime: FATAL EXCEPTION: main
Processus: abff.fxguide, PID: 29076
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 100 path $.pathData.
à com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
à com.google.gson.Gson.fromJson(Gson.java:879)
à com.google.gson.Gson.fromJson(Gson.java:844)
à com.google.gson.Gson.fromJson(Gson.java:793)
à com.google.gson.Gson.fromJson(Gson.java:765)
à abff.fxguide.Tour_Helper.getTourFromJson(Tour_Helper.java:311)
à abff.fxguide.Tour_All.showTourDetails(Tour_All.java:266)
à abff.fxguide.Tour_All$8.onClick(Tour_All.java:370)
à android.view.View.performClick(View.java:4856)
à android.view.View$PerformClick.run(View.java:19956)
à android.os.Handler.handleCallback(Handler.java:739)
à android.os.Handler.dispatchMessage(Handler.java:95)
à android.os.Looper.loop(Looper.java:211)
à android.app.ActivityThread.main(ActivityThread.java:5389)
à java.lang.reflect.Method.invoke(Méthode native)
à java.lang.reflect.Method.invoke(Method.java:372)
à com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1020)
à com.android.internal.os.ZygoteInit.main(ZygoteInit.java:815)
Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 100 path $.pathData.
à com.google.gson.stream.JsonReader.beginObject(JsonReader.java:388)
à com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:183)
à com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:145)
à com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:40)
à com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:186)
à com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:145)
à com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:116)
à com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:216)
à com.google.gson.Gson.fromJson(Gson.java:879) 
à com.google.gson.Gson.fromJson(Gson.java:844) 
à com.google.gson.Gson.fromJson(Gson.java:793) 
à com.google.gson.Gson.fromJson(Gson.java:765) 
à abff.fxguide.Tour_Helper.getTourFromJson(Tour_Helper.java:311) 
à abff.fxguide.Tour_All.showTourDetails(Tour_All.java:266) 
à abff.fxguide.Tour_All$8.onClick(Tour_All.java:370) 
à android.view.View.performClick(View.java:4856) 
à android.view.View$PerformClick.run(View.java:19956) 
à android.os.Handler.handleCallback(Handler.java:739) 
à android.os.Handler.dispatchMessage(Handler.java:95) 
à android.os.Looper.loop(Looper.java:211) 
à android.app.ActivityThread.main(ActivityThread.java:5389) 
à java.lang.reflect.Method.invoke(Méthode native) 
à java.lang.reflect.Method.invoke(Method.java:372) 
à com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1020) 
à com.android.internal.os.ZygoteInit.main(ZygoteInit.java:815)

J'aimerais savoir si l'objet principal (Tour_Object) est désérialisable comme indiqué dans getTourFromJson(String JsonString) lors de l'utilisation d'attributs tels que pathData?

1voto

Sergi Points 537

TL;DR Vous avez besoin d'un adaptateur.

Gson peut fonctionner avec Map sans adaptateur explicite mais il s'attend juste à ce que la clé soit une String car il traduit la carte en un objet JSON. S'il ne s'agit pas d'une String, il la convertit généralement en appelant simplement Object#toString().

Autrement dit : quelle est la représentation JSON de votre objet ? Pas évident, car vous ne pouvez pas avoir d'objets complexes en tant que propriétés d'un objet JSON, ils sont toujours des chaînes de caractères.

Vous devez donc décider de votre représentation et écrire un adaptateur personnalisé pour cela.

Réponse bonus étendue

En bonus, je peux vous suggérer de penser à quelque chose comme un tableau pour représenter vos pathData. Au lieu d'une carte, vous écrivez un tableau d'objets qui sont des paires clé/valeur. Quelque chose comme ceci :

{
    "pathData": [
        {
            "pair": {"a": "one", "b": "two"},
            "map": { ... }
        },
        {
            "pair": {"a": "one", "b": "two"},
            "map": { ... }
        }
    ]
}

pair est votre Pair et map est votre Map.

Ensuite, vous pourriez écrire l'adaptateur TypeAdapter comme ceci.

package net.sargue.gson;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class SO36716159 {
  public static void main(String[] args) {
    Tour_Object o = new Tour_Object();
    o.pathData.put(new Pair<>("one", "two"), Collections.emptyMap());
    Map myMap = new HashMap<>();
    myMap.put("a", "b");
    myMap.put("c", "d");
    o.pathData.put(new Pair<>("three", "four"), myMap);

    Gson gson = new GsonBuilder()
            .registerTypeAdapter(Tour_Object.class,
                                 new TourObjectAdapter())
            .setPrettyPrinting()
            .create();
    String json = gson.toJson(o);
    System.out.println("json = " + json);
  }

  public static class Pair {
    private A a;
    private B b;

    public Pair(A a, B b) {
      this.a = a;
      this.b = b;
    }
  }

  public static class Tour_Object {
    private Map, Map> pathData = new HashMap<>();

    public Map, Map> pathData() {
      return pathData;
    }
  }

  public static class TourObjectAdapter extends TypeAdapter {
    @Override
    public void write(JsonWriter out, Tour_Object value)
            throws IOException
    {
      Gson gson = new Gson();

      out.beginObject()
         .name("pathData")
         .beginArray();

      for (Map.Entry, Map> entry :
              value.pathData().entrySet()) {

        out.beginObject();
        out.name("pair");
        gson.getAdapter(new TypeToken>() {})
            .write(out, entry.getKey());
        out.name("map");
        gson.getAdapter(new TypeToken>() {})
            .write(out, entry.getValue());
        out.endObject();
      }
      out.endArray()
         .endObject();
    }

    @Override
    public Tour_Object read(JsonReader in) throws IOException {
      throw new RuntimeException("Left as an exercise for the reader... ;-)");
    }
  }
}

Remarque : ce n'est pas un exemple complet, seulement la sérialisation et sans trop de vérifications, etc. Valide uniquement comme exemple.

La sortie du code précédent est :

json = {
  "pathData": [
    {
      "pair": {
        "a": "one",
        "b": "two"
      },
      "map": {}
    },
    {
      "pair": {
        "a": "three",
        "b": "four"
      },
      "map": {
        "a": "b",
        "c": "d"
      }
    }
  ]
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X