web-dev-qa-db-fra.com

Comment puis-je empêcher gson de convertir des entiers en doubles

J'ai des entiers dans mon json, et je ne veux pas que gson les convertisse en doubles. Ce qui suit ne fonctionne pas:

@Test
public void keepsIntsAsIs(){
    String json="[{\"id\":1,\"quantity\":2,\"name\":\"Apple\"},{\"id\":3,\"quantity\":4,\"name\":\"orange\"}]";
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Double.class,  new DoubleSerializerAsInt());
    Gson gson = gsonBuilder.create();
    List<Map<String, Object>> l = gson.fromJson(json, List.class);
    for(Map<String, Object> item : l){
        System.out.println(item);
    }
}

private static class DoubleSerializerAsInt implements JsonSerializer<Double>{

    @Override
    public JsonElement serialize(Double aDouble, Type type, JsonSerializationContext jsonSerializationContext) {
        int value = (int)Math.round(aDouble);
        return new JsonPrimitive(value);
    }
}

La sortie n'est pas ce que je veux:

{id=1.0, quantity=2.0, name=Apple}
{id=3.0, quantity=4.0, name=orange}

Existe-t-il un moyen d'avoir des entiers au lieu de doubles dans ma carte?

{id=1, quantity=2, name=Apple}
{id=3, quantity=4, name=orange}

Edit: tous mes champs ne sont pas entiers. J'ai modifié mon exemple en conséquence. J'ai lu pas mal d'exemples en ligne, y compris quelques réponses sur ce site, mais cela ne fonctionne pas dans ce cas particulier.

15
AlexC

1) Vous devez créer un JsonDeserializer personnalisé et non JsonSerializer comme dans votre question.

2) Je ne pense pas que ce comportement vienne du désérialiseur Double. c'est plus comme un problème json objet/carte

Voici de code source :

case NUMBER:
      return in.nextDouble();

Vous pouvez donc essayer l'approche avec un désérialiseur personnalisé pour Map<String, Object> (ou une carte plus générique si vous le souhaitez):

public static class MapDeserializerDoubleAsIntFix implements JsonDeserializer<Map<String, Object>>{

    @Override  @SuppressWarnings("unchecked")
    public Map<String, Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        return (Map<String, Object>) read(json);
    }

    public Object read(JsonElement in) {

        if(in.isJsonArray()){
            List<Object> list = new ArrayList<Object>();
            JsonArray arr = in.getAsJsonArray();
            for (JsonElement anArr : arr) {
                list.add(read(anArr));
            }
            return list;
        }else if(in.isJsonObject()){
            Map<String, Object> map = new LinkedTreeMap<String, Object>();
            JsonObject obj = in.getAsJsonObject();
            Set<Map.Entry<String, JsonElement>> entitySet = obj.entrySet();
            for(Map.Entry<String, JsonElement> entry: entitySet){
                map.put(entry.getKey(), read(entry.getValue()));
            }
            return map;
        }else if( in.isJsonPrimitive()){
            JsonPrimitive prim = in.getAsJsonPrimitive();
            if(prim.isBoolean()){
                return prim.getAsBoolean();
            }else if(prim.isString()){
                return prim.getAsString();
            }else if(prim.isNumber()){

                Number num = prim.getAsNumber();
                // here you can handle double int/long values
                // and return any type you want
                // this solution will transform 3.0 float to long values
                if(Math.ceil(num.doubleValue())  == num.longValue())
                   return num.longValue();
                else{
                    return num.doubleValue();
                }
           }
        }
        return null;
    }
}

Pour l'utiliser, vous devrez donner les bons TypeToken à registerTypeAdapter et gson.fromJson fonction:

String json="[{\"id\":1,\"quantity\":2,\"name\":\"Apple\"}, {\"id\":3,\"quantity\":4,\"name\":\"orange\"}]";

GsonBuilder gsonBuilder = new GsonBuilder();

gsonBuilder.registerTypeAdapter(new TypeToken<Map <String, Object>>(){}.getType(),  new MapDeserializerDoubleAsIntFix());

Gson gson = gsonBuilder.create();
List<Map<String, Object>> l = gson.fromJson(json, new TypeToken<List<Map<String, Object>>>(){}.getType() );

for(Map<String, Object> item : l)
    System.out.println(item);

String serialized = gson.toJson(l);
System.out.println(serialized);

Résultat:

{id=1, quantity=2, name=Apple}
{id=3, quantity=4, name=orange}
Serialized back to: [{"id":1,"quantity":2,"name":"Apple"},{"id":3,"quantity":4,"name":"orange"}]

PS: Ce n'est qu'une option de plus que vous pouvez essayer. Personnellement, j'ai envie de créer un objet personnalisé pour votre json au lieu de List<Map<String, Integer>> est beaucoup plus cool et plus facile à lire

17
varren

Version en streaming de la réponse de @ varren:

class CustomizedObjectTypeAdapter extends TypeAdapter<Object> {

    private final TypeAdapter<Object> delegate = new Gson().getAdapter(Object.class);

    @Override
    public void write(JsonWriter out, Object value) throws IOException {
        delegate.write(out, value);
    }

    @Override
    public Object read(JsonReader in) throws IOException {
        JsonToken token = in.peek();
        switch (token) {
            case BEGIN_ARRAY:
                List<Object> list = new ArrayList<Object>();
                in.beginArray();
                while (in.hasNext()) {
                    list.add(read(in));
                }
                in.endArray();
                return list;

            case BEGIN_OBJECT:
                Map<String, Object> map = new LinkedTreeMap<String, Object>();
                in.beginObject();
                while (in.hasNext()) {
                    map.put(in.nextName(), read(in));
                }
                in.endObject();
                return map;

            case STRING:
                return in.nextString();

            case NUMBER:
                //return in.nextDouble();
                String n = in.nextString();
                if (n.indexOf('.') != -1) {
                    return Double.parseDouble(n);
                }
                return Long.parseLong(n);

            case BOOLEAN:
                return in.nextBoolean();

            case NULL:
                in.nextNull();
                return null;

            default:
                throw new IllegalStateException();
        }
    }
}

Il s'agit d'une version modifiée de ObjectTypeAdapter.Java . Ces lignes originales:

case NUMBER:
    return in.nextDouble();

sont remplacés par ce qui suit:

case NUMBER:
    String n = in.nextString();
    if (n.indexOf('.') != -1) {
        return Double.parseDouble(n);
    }
    return Long.parseLong(n);

Dans ce code, nombre est lu sous forme de chaîne et le type de nombre est sélectionné en fonction de l'existence de point: nombre n'est double que s'il a un point dans sa représentation de chaîne et qu'il est long sinon. Cette solution conserve les valeurs d'origine du JSON source.

Cet adaptateur modifié pourrait être utilisé comme universel si vous pouviez l'enregistrer pour le type d'objet mais Gson l'empêche:

// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);

Vous devez enregistrer cet adaptateur de type pour les types dont vous avez besoin, par exemple Map et List:

CustomizedObjectTypeAdapter adapter = new CustomizedObjectTypeAdapter();
Gson gson = new GsonBuilder()
        .registerTypeAdapter(Map.class, adapter)
        .registerTypeAdapter(List.class, adapter)
        .create();

Maintenant, Gson peut désérialiser les nombres tels quels .

9
cybersoft

Vous devez utiliser public T fromJson (JsonElement json, Type typeOfT)

public void keepsIntsAsIs(){
        String json="[{\"id\":1,\"quantity\":2},{\"id\":3,\"quantity\":4}]";
        GsonBuilder gsonBuilder = new GsonBuilder();
        Gson gson = gsonBuilder.create();
        Type objectListType = new TypeToken<List<Map<String, Integer>>>(){}.getType();
        List<Map<String, Integer>> l = gson.fromJson(json, objectListType);
        for(Map<String, Integer> item : l){
            System.out.println(item);
        }
    }

Production:

{id=1, quantity=2}
{id=3, quantity=4}

[ÉDITER]

Si tous les champs ne sont pas des entiers, une façon de résoudre ce problème consiste à mapper le json à un objet et à définir un désérialiseur pour cet objet.

Voici l'exemple.

Je mappe json sur IdQuantityName et IdQuantityDeserializer est le désérialiseur json.

package com.foo;



import Java.lang.reflect.Type;
import Java.util.List;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.reflect.TypeToken;

public class TestGSON {

public void keepsIntsAsIs(){
    String json="[{\"id\":1,\"quantity\":2,\"name\":\"Apple\"},{\"id\":3,\"quantity\":4,\"name\":\"orange\"}]";
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeHierarchyAdapter(IdQuantityName.class, new IdQuantityDeserializer());
    gsonBuilder.registerTypeAdapter(IdQuantityName.class, new IdQuantityDeserializer());

    Gson gson = gsonBuilder.create();
    Type objectListType = new TypeToken<List<IdQuantityName>>(){}.getType();
    List<IdQuantityName> l = gson.fromJson(json,objectListType);
    for (IdQuantityName idQuantityName : l) {
        System.out.println(idQuantityName);
    }
}



class IdQuantityName{
    private int id;
    private Object quantity;
    private String name;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public Object getQuantity() {
        return quantity;
    }
    public void setQuantity(Object quantity) {
        this.quantity = quantity;
    }
    public Object getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "IdQuantityName [id=" + id + ", quantity=" + quantity
                + ", name=" + name + "]";
    }



}
private  class IdQuantityDeserializer implements JsonDeserializer<IdQuantityName>{

    @Override
    public IdQuantityName deserialize(JsonElement json, Type typeOfT,
            JsonDeserializationContext context) throws JsonParseException {

        JsonObject jo = json.getAsJsonObject();

        IdQuantityName idq = new IdQuantityName();
        idq.setId(jo.get("id").getAsInt());
        idq.setName(jo.get("name").getAsString());

        JsonElement jsonElement = jo.get("quantity");
        if(jsonElement instanceof JsonPrimitive){
            if(((JsonPrimitive) jsonElement).isNumber()){
                idq.setQuantity(jsonElement.getAsInt());
            };
        }
        return idq;

    }
}
public static void main(String[] args) {
    new TestGSON().keepsIntsAsIs();
}
}
3
Sanj

Cela fonctionne bien pour moi:

private static class DoubleSerializer implements JsonSerializer<Double> {
    @Override
    public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
        return src == src.longValue() ? new JsonPrimitive(src.longValue()) : new JsonPrimitive(src);
    }
}

Gson gson = new GsonBuilder().registerTypeAdapter(Double.class, new DoubleSerializer()).setPrettyPrinting().create();
1
Leon

Utilisez Jackson au lieu de Gson, cela résout votre problème:

import com.fasterxml.jackson.databind.ObjectMapper;

import Java.io.IOException; import Java.util.Map;

classe public JacksonMapExample1 {

public static void main(String[] args) {

    ObjectMapper mapper = new ObjectMapper();
    String json = "{\"name\":\"mkyong\", \"age\":\"37\"}";

    try {

        // convert JSON string to Map
        Map<String, String> map = mapper.readValue(json, Map.class);

        // it works
        //Map<String, String> map = mapper.readValue(json, new TypeReference<Map<String, String>>() {});

        System.out.println(map);

    } catch (IOException e) {
        e.printStackTrace();
    }

}

}

0
nitinsridar

Cela a fonctionné pour moi, j'ai un champ "spécifications" qui est un Map<String, Object>:

public class MyClass {

  public Map<String, Object> specs;

}

Avant le correctif, j'obtenais cette sortie pour une liste de ces objets:

{  
   "hits":{  
      "content":[  
         {  
            "specs":{  
               "fiscalHorsePower":4.0,
               "nbOfDoors":5.0,
               "consumption":4.3
            }
         }
      ]
   }
}

fiscalHorsePower et nbOfDoors sont des nombres entiers.

Voici le correctif que j'ai utilisé, créez d'abord un nouveau Adapter et un Factory:

public class CustomizedObjectTypeAdapter extends TypeAdapter<Object> {

public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
    @SuppressWarnings("unchecked")
    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        if (Map.class.isAssignableFrom(type.getRawType())) {
            return (TypeAdapter<T>) new CustomizedObjectTypeAdapter();
        }
        return null;
    }
};

private final TypeAdapter<Object> delegate = new Gson().getAdapter(Object.class);

@Override
public void write(JsonWriter out, Object value) throws IOException {
    delegate.write(out, value);
}

@Override
public Object read(JsonReader in) throws IOException {
    JsonToken token = in.peek();
    switch (token) {
        case BEGIN_ARRAY:
            List<Object> list = new ArrayList<Object>();
            in.beginArray();
            while (in.hasNext()) {
                list.add(read(in));
            }
            in.endArray();
            return list;

        case BEGIN_OBJECT:
            Map<String, Object> map = new LinkedTreeMap<String, Object>();
            in.beginObject();
            while (in.hasNext()) {
                map.put(in.nextName(), read(in));
            }
            in.endObject();
            return map;

        case STRING:
            return in.nextString();

        case NUMBER:
            //return in.nextDouble();
            String n = in.nextString();
            if (n.indexOf('.') != -1) {
                return Double.parseDouble(n);
            }
            return Long.parseLong(n);

        case BOOLEAN:
            return in.nextBoolean();

        case NULL:
            in.nextNull();
            return null;

        default:
            throw new IllegalStateException();
    }
}
}

Et puis enregistrez l'usine:

Gson gson = new GsonBuilder().registerTypeAdapterFactory(CustomizedObjectTypeAdapter.FACTORY);

et voici le résultat avec le correctif:

{  
   "hits":{  
      "content":[  
         {  
            "specs":{  
               "fiscalHorsePower":4,
               "nbOfDoors":5,
               "consumption":4.3
            }
         }
      ]
   }
}
0
anthofo