2010-12-28 4 views
11

Chcę serializować moją przykładową klasę poniżej w JSON przy użyciu GSON.Jak serializować mapę mapy za pomocą GSON?

import com.google.gson.Gson; 
import com.google.gson.GsonBuilder; 
import java.util.LinkedHashMap; 

public class Example 
{ 
    private LinkedHashMap<String,Object> General; 

    private static final String VERSION="Version"; 
    private static final String RANGE="Range"; 
    private static final String START_TIME="Start_Time"; 
    private static final String END_TIME="End_Time"; 

    public Example() { 
     General = new LinkedHashMap<String,Object>(); 
     General.put(VERSION, "0.1"); 

     LinkedHashMap<String,String> Range = new LinkedHashMap<String, String>(); 
     Range.put(START_TIME, "now"); 
     Range.put(END_TIME, "never"); 

     General.put(RANGE, Range); 
    } 

    public String toJSON() { 
     Gson gson = new GsonBuilder().serializeNulls().create(); 
     return gson.toJson(this); 
    } 
} 

Spodziewałem się uzyskać następujące dane wyjściowe:

{"General":{"Version":"0.1","Range":{"Start_Time":"now","End_Time":"never"}}} 

Ale wywołanie funkcji toJSON() powraca

{"General":{"Version":"0.1","Range":{}}} 

Wydaje się, że GSON nie może serializacji Mapa Range wewnątrz Mapie General. Czy to ograniczenie GSON, czy też robię coś nie tak?

+2

Jeśli inne odpowiedzi są poprawny i domyślny moduł do mapowania map nie obsługuje map zagnieżdżonych, zgłosiłbym raport błędu dla zespołu GSON. Wygląda na to, że naprawdę źle mi się domyślam; przynajmniej powinien rzucić wyjątek zamiast po cichutku połykać zawartość. – StaxMan

+0

+1 @StaxMan Wydaje się, że tak. Widziałem to wcześniej, wymyśliłem trochę pracy. Jeśli jest jakiś sposób, aby to zrobić, warto dokumentować. – Nishant

Odpowiedz

8

Powodem Nishant's answer prace dlatego konstruktor domyślny Gson umożliwia wszelkiego rodzaju rzeczy domyślnie, że można inaczej trzeba ręcznie enably pomocą GsonBuilder.

Z JavaDocs:

Tworzy obiekt Gson z domyślnej konfiguracji. Domyślna konfiguracja ma następujące ustawienia:

  • JSON generowany przez metody toJson ma zwartą reprezentację. Oznacza to, że cała niepotrzebna biała przestrzeń zostaje usunięta. Możesz zmienić to zachowanie za pomocą GsonBuilder.setPrettyPrinting().
  • Wygenerowany JSON pomija wszystkie pola, które są puste. Zauważ, że wartości null w tablicach są zachowywane, ponieważ tablica jest uporządkowaną listą. Co więcej, jeśli pole nie ma wartości NULL, ale wygenerowany JSON jest pusty, pole jest zachowywane. Możesz skonfigurować Gson do serializowania wartości null, ustawiając GsonBuilder.serializeNulls().
  • Gson udostępnia domyślną serializację i deserializację dla wyliczeń, mapy, java.net.URL, java.net.URI, java.util.Locale, java.util.Date, java.math.BigDecimal i java.math.BigInteger klasy. Jeśli wolisz zmienić domyślną reprezentację, możesz to zrobić, rejestrując adapter typu za pośrednictwem GsonBuilder.registerTypeAdapter (Type, Object).
  • Domyślny format daty jest taki sam jak java.text.DateFormat.DEFAULT. Ten format ignoruje milisekundową część daty podczas serializacji. Możesz to zmienić, wywołując GsonBuilder.setDateFormat (int) lub GsonBuilder.setDateFormat (String).
  • Domyślnie Gson ignoruje com.google.gson.annotations.Expose adnotację. Możesz włączyć Gson do serializacji/deserializacji tylko te pola oznaczone tą adnotacją przez GsonBuilder.excludeFieldsWithoutExposeAnnotation().
  • Domyślnie Gson ignoruje com.google.gson.annotations.Od adnotacji. Możesz włączyć Gson, aby użyć tej adnotacji poprzez GsonBuilder.setVersion (double).
  • Domyślna strategia nazewnictwa pól dla wyjścia Json jest taka sama jak w Javie. Tak więc klasa fieldName numberNumer będzie wyprowadzana jako "versionNumber @ quot; w Json.Te same zasady są stosowane do mapowania przychodzących Json do klas Java.Można zmienić te zasady poprzez GsonBuilder.setFieldNamingPolicy (FieldNamingPolicy).
  • domyślne Gson wyklucza przemijające lub statycznych pól z uwzględnieniem dla serializacji i deserializacji. można to zmienić poprzez GsonBuilder.excludeFieldsWithModifiers (int).

OK, teraz widzę, jaki problem jest. Domyślny program do serializacji map, zgodnie z oczekiwaniami, nie obsługuje zagnieżdżonych map. Jak widać w this source snippet from DefaultTypeAdapters (szczególnie jeśli przejdziesz przez debugger) zmienna childGenericType jest ustawiona na typ java.lang.Object z jakiegoś tajemniczego powodu, więc typ środowiska wykonawczego wartości nigdy nie jest analizowany.

dwa rozwiązania, myślę:

  1. Implement your own Map serializer/deserializer
  2. użyć bardziej skomplikowanej wersji swojej metody, coś takiego:

    public String toJSON(){ 
        final Gson gson = new Gson(); 
        final JsonElement jsonTree = gson.toJsonTree(General, Map.class); 
        final JsonObject jsonObject = new JsonObject(); 
        jsonObject.add("General", jsonTree); 
        return jsonObject.toString(); 
    } 
    
+0

Przepraszam, mój pierwszy komentarz do odpowiedzi Nishanta był błędny. Jego sugestia nie działa całkowicie dla mnie. – asmaier

+0

@asmaier ok, dodano trochę więcej treści do mojej odpowiedzi –

+0

co dziwne, jeśli zamienisz LinkedHashMap na ImmutableMap, to działa dobrze. – Nishant

4

Spróbuj tego:

Gson gson = new Gson(); 
System.out.println(gson.toJson(General)); 

Nie wiesz, jeśli nadal szuka rozwiązania, to działa na mnie:

import java.util.LinkedHashMap; 

import com.google.common.collect.ImmutableMap; 
import com.google.gson.Gson; 
import com.google.gson.GsonBuilder; 

public class Example { 
// private static LinkedHashMap<String,Object> General; 
    private ImmutableMap General; 

    private static final String VERSION="Version"; 
    private static final String RANGE="Range"; 
    private static final String START_TIME="Start_Time"; 
    private static final String END_TIME="End_Time"; 

    public Example() { 

     LinkedHashMap<String,String> Range = new LinkedHashMap<String, String>(); 
     Range.put(START_TIME, "now"); 
     Range.put(END_TIME, "never"); 

//  General.put(RANGE, Range); 
     General = ImmutableMap.of(VERSION, "0.1", RANGE, Range); 
    } 

    public String toJSON() { 
//  Gson gson = new GsonBuilder().serializeNulls().create(); 
      Gson gson = new Gson(); 
      return gson.toJson(this); 
    } 

} 

powroty: { "General": {” Wersja ":" 0.1 "," Zakres ": {" Czas rozpoczęcia ":" teraz "," Czas zakończenia ":" nigdy "}}}


Oczywiście można użyć ImmutableMap.copyOf(your_hashmap)here zamiast

+1

Działa, ale dlaczego? Również jest to trochę niewygodne. Jeśli chcę dodać więcej map do mojej klasy Przykład, muszę napisać w mojej funkcji do JSON() coś w stylu 'String out = gson.toJson (this.General); out + = gson.toJson (this.Map2); out + = gson.toJson (this.map3); ... powrócić; ' – asmaier

+0

nie wiem kumpla. Miałem ten problem, poszedłem tą drogą. Zakładam, że przyczyną jest http://sites.google.com/site/gson/gson-user-guide#TOC-Nested-Classes-including-Inner-Clas – Nishant

+0

Niestety, mój pierwszy komentarz był nieprawidłowy i nie mogę edytować to już. Użycie 'gson.toJson (General)' zwraca '{" Version ":" 0.1 "," Range ": {" Start_Time ":" bla "," End_Time ":" blu "}," Suma kontrolna ":" + 1 " } '. Ale chcę mieć '{" General ": {" Version ":" 0.1 ", ....'. więc twoje rozwiązanie nie działa całkowicie dla mnie. – asmaier

1

Prostszym rozwiązaniem byłoby użyj Jackson zamiast GSON, serializacja zagnieżdżonej mapy działa po wyjęciu z pudełka:

LinkedHashMap<String, Object> general; 
    general = new LinkedHashMap<String, Object>(); 
    general.put("Version", "0.1"); 

    LinkedHashMap<String, String> Range = new LinkedHashMap<String, String>(); 
    Range.put("Start_Time", "now"); 
    Range.put("End_Time", "never"); 

    general.put("Range", Range); 

    // Serialize the map to json using Jackson 
    ByteArrayOutputStream os = new ByteArrayOutputStream(); 
    new org.codehaus.jackson.map.ObjectMapper().writer().writeValue(os, 
      general);  
    String json = os.toString(); 
    os.close(); 

    System.out.println(json); 

wyjściowa:

{ "Version": "0.1", "Range": { "start_time": "teraz", "end_time": "nigdy"}}