Как я могу запретить gson преобразовывать целые числа в двойные
у меня есть целые числа в моем json, и я не хочу, чтобы gson преобразовывал их в двойники. Не работает следующее:
@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);
}
}
выход не то, что я хочу:
{id=1.0, quantity=2.0, name=apple}
{id=3.0, quantity=4.0, name=orange}
есть ли способ иметь целые числа вместо двойников в моей карте?
{id=1, quantity=2, name=apple}
{id=3, quantity=4, name=orange}
Edit: не все мои поля целые. Я соответствующим образом изменил свой пример. Я прочитал довольно много примеров в интернете, включая некоторые ответы на этом сайте, но это не работает в этом конкретный случай.
4 ответов
1) Вы должны создать пользовательский JsonDeserializer
, а не JsonSerializer
как в вашем вопросе.
2) я не думаю, что это поведение исходит из Double
десериализатор. это больше похоже на JSON object/map problem
отсюда исходный код:
case NUMBER:
return in.nextDouble();
так можно попробовать подход с пользовательских десериализатор для Map<String, Object>
(или более общая карта, если хотите) :
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;
}
}
чтобы использовать его, вам придется дать правильный TypeToken
to registerTypeAdapter
и gson.fromJson
функция:
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);
результат:
{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: Это еще один вариант можно попробовать. Лично я чувствую, что создаю пользовательский объект для вашего json вместо List<Map<String, Integer>>
намного круче и легче читать путь
потоковая версия ответа @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();
}
}
}
- это доработанная версия ObjectTypeAdapter.java. Эти оригинальные строки:
case NUMBER:
return in.nextDouble();
заменяются следующим образом:
case NUMBER:
String n = in.nextString();
if (n.indexOf('.') != -1) {
return Double.parseDouble(n);
}
return Long.parseLong(n);
в этом коде число читается как строка, а тип числа выбирается на основе существования точки: число двойное, только если в его строковом представлении есть точка, а в противном случае оно длинное. Такое решение сохраняет исходные значения источника формат JSON.
этот измененный адаптер может использоваться как универсальный, если вы можете зарегистрировать его для типа объекта, но Gson предотвращает его:
// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);
вы должны зарегистрировать этот адаптер типа для тех типов, которые вам нужны, например Map
и List
:
CustomizedObjectTypeAdapter adapter = new CustomizedObjectTypeAdapter();
Gson gson = new GsonBuilder()
.registerTypeAdapter(Map.class, adapter)
.registerTypeAdapter(List.class, adapter)
.create();
теперь Gson может десериализовать числа как.
вы должны использовать 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);
}
}
выход:
{id=1, quantity=2}
{id=3, quantity=4}
[EDIT]
если не все поля являются целыми числами, то один из способов решить эту проблему-сопоставить json объекту и определить десериализатор для этого объекта.
Ниже приведен пример.
я сопоставляю json с IdQuantityName
и IdQuantityDeserializer
является десериализатором 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();
}
}
это отлично работает для меня:
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();