2010-12-28 24 views
11

Quiero serializar mi clase Ejemplo a continuación en JSON usando GSON.¿Cómo serializar un mapa de un mapa con 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); 
    } 
} 

me espera para obtener el siguiente resultado:

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

Pero llamar a la función toJSON() vuelve

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

Parece que GSON no puede serializar el Mapa Range dentro del mapa General. ¿Es esto una limitación de GSON o estoy haciendo algo mal aquí?

+2

Si otras respuestas son el serializador de mapas correcto y predeterminado no manejará los mapas anidados, presentaría un informe de error para el equipo GSON. Parece que realmente malo me falla; al menos debería arrojar una excepción en lugar de tragar en voz baja los contenidos. – StaxMan

+0

+1 @StaxMan parece que sí. He visto esto antes, se me ocurrió algo de trabajo. Si hay alguna forma de hacerlo, vale la pena documentarse. – Nishant

Respuesta

8

La razón por la Nishant's answer funciona es porque constructor por defecto de Gson permite todo tipo de cosas por defecto que de otra manera que de forma manual utilizando el enably GsonBuilder.

Desde el JavaDocs:

construye un objeto Gson con configuración predeterminada. La configuración predeterminada tiene la siguiente configuración:

  • El JSON generado por los métodos toJson está en una representación compacta. Esto significa que se elimina todo el espacio en blanco innecesario. Puede cambiar este comportamiento con GsonBuilder.setPrettyPrinting().
  • El JSON generado omite todos los campos que son nulos. Tenga en cuenta que los nulos en las matrices se mantienen como están, ya que una matriz es una lista ordenada. Además, si un campo no es nulo, pero su JSON generado está vacío, el campo se mantiene. Puede configurar Gson para serializar valores nulos configurando GsonBuilder.serializeNulls().
  • Gson proporciona serialización y deserialización predeterminadas para Enums, Map, java.net.URL, java.net.URI, java.util.Locale, java.util.Date, java.math.BigDecimal y java.math.BigInteger clases Si prefiere cambiar la representación predeterminada, puede hacerlo registrando un adaptador de tipo mediante GsonBuilder.registerTypeAdapter (Type, Object).
  • El formato de fecha predeterminado es el mismo que java.text.DateFormat.DEFAULT. Este formato ignora la porción de milisegundos de la fecha durante la serialización. Puede cambiar esto invocando GsonBuilder.setDateFormat (int) o GsonBuilder.setDateFormat (String).
  • De forma predeterminada, Gson ignora la com.google.gson.annotations.Expose annotation. Puede habilitar Gson para serializar/deserializar solo aquellos campos marcados con esta anotación a través de GsonBuilder.excludeFieldsWithoutExposeAnnotation().
  • De forma predeterminada, Gson ignora las com.google.gson.annotaciones.Desde la anotación. Puede habilitar Gson para usar esta anotación a través de GsonBuilder.setVersion (doble).
  • La política de nomenclatura de campo predeterminada para el Json de salida es la misma que en Java. Por lo tanto, un campo de clase Java versionNumber se generará como "versionNumber @" en Json. Las mismas reglas se aplican para asignar Json entrante a las clases de Java. Puede cambiar esta política a través de GsonBuilder.setFieldNamingPolicy (FieldNamingPolicy).
  • By de manera predeterminada, Gson excluye campos transitorios o estáticas de la consideración para la serialización y deserialización. puede cambiar este comportamiento a través de GsonBuilder.excludeFieldsWithModifiers (int).

OK, ahora veo cuál es el problema. el el serializador de mapas predeterminado, como esperaba, no admite mapas anidados. Como puede ver en this source snippet from DefaultTypeAdapters (especialmente si avanza con un depurador) la variable childGenericType se establece en el tipo java.lang.Object por algún motivo misterioso, por lo que el tipo de tiempo de ejecución del valor nunca se analiza.

dos soluciones, supongo:

  1. Implement your own Map serializer/deserializer
  2. utilizar una versión más complicada de su método, algo como esto:

    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

Lo siento, mi primer comentario a la respuesta de Nishant fue incorrecto. Su sugerencia no funciona completamente para mí. – asmaier

+0

@asmaier bien, agregué un poco más de contenido a mi respuesta –

+0

extrañamente, si reemplaza LinkedHashMap con ImmutableMap, funciona bien. – Nishant

4

Prueba esto:

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

No estoy seguro de si todavía está buscando una solución, esto funciona para mí:

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); 
    } 

} 

rendimientos: { "General": {" versión ":" 0.1" , "Rango": { "Start_Time": "ahora", "hora_final": "nunca"}}}


Obviamente se podría utilizar en lugar ImmutableMap.copyOf(your_hashmap)here

+1

Funciona, pero ¿por qué? También es un poco inconveniente. Si quiero agregar más mapas a mi clase Ejemplo, tengo que escribir en mi función toJSON() algo como 'String out = gson.toJson (this.General); out + = gson.toJson (this.Map2); out + = gson.toJson (this.map3); ... regresa; ' – asmaier

+0

no estoy seguro amigo. Tuve este problema, fui por esta ruta. Supuse que la causa era esta http://sites.google.com/site/gson/gson-user-guide#TOC-Nested-Classes-including-Inner-Clas – Nishant

+0

Lo siento, mi primer comentario fue incorrecto y no puedo editarlo es más. Usando 'gson.toJson (General)' devuelve '{" Version ":" 0.1 "," Range ": {" Start_Time ":" bla "," End_Time ":" blu "}," Checksum ":" + 1 " } ' Pero quiero tener '{" General ": {" Version ":" 0.1 ", ....'. entonces tu solución no funciona completamente para mí. – asmaier

1

Una alternativa más sencilla sería la de use Jackson en lugar de GSON, la serialización de un mapa anidado funciona de la caja:

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); 

Salida:

{ "versión": "0.1", "Rango": { "Start_Time": "ahora", "hora_final": "nunca"}}

Cuestiones relacionadas