Gson now deserializes numbers to the appropriate Number subclass

This commit is contained in:
Marc Baloup 2023-10-08 23:57:46 +02:00
parent db06ab1be9
commit a24eab67b6
2 changed files with 68 additions and 4 deletions

View File

@ -2,10 +2,14 @@ package fr.pandacube.lib.core.json;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.ToNumberStrategy;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.MalformedJsonException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
@ -16,6 +20,52 @@ import java.util.function.Function;
*/
public class Json {
/**
* Makes Gson deserialize numbers to Number subclasses the same way SnakeYAML does
*/
private static final ToNumberStrategy YAML_EQUIVALENT_NUMBER_STRATEGY = in -> {
String value = in.nextString();
// YAML uses Regex to resolve values as INT or FLOAT (see org.yaml.snakeyaml.resolver.Resolver), trying FLOAT first.
// We see in the regex that FLOAT MUST have a "." in the string, but INT must not, so we try that.
boolean isFloat = value.contains(".");
if (isFloat) {
// if float, will only parse to Double
// (see org.yaml.snakeyaml.constructor.SafeConstructor.ConstructYamlFloat)
try {
Double d = Double.valueOf(value);
if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) {
throw new MalformedJsonException("JSON forbids NaN and infinities: " + d + "; at path " + in.getPreviousPath());
}
return d;
} catch (NumberFormatException e) {
throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPreviousPath(), e);
}
}
else {
// if integer, will try to parse int, then long, then BigDecimal
// (see org.yaml.snakeyaml.constructor.SafeConstructor.ConstructYamlInt
// then org.yaml.snakeyaml.constructor.SafeConstructor.createNumber)
try {
return Integer.valueOf(value);
} catch (NumberFormatException e) {
try {
return Long.valueOf(value);
} catch (NumberFormatException e2) {
try {
return new BigInteger(value);
} catch (NumberFormatException e3) {
throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPreviousPath(), e3);
}
}
}
}
};
/**
* {@link Gson} instance with {@link GsonBuilder#setLenient()} and support for Java records and additional
* {@link TypeAdapterFactory} provided with {@link #registerTypeAdapterFactory(TypeAdapterFactory)}.
@ -53,6 +103,7 @@ public class Json {
GsonBuilder base = new GsonBuilder()
.registerTypeAdapterFactory(new CustomAdapterFactory())
.disableHtmlEscaping()
.setObjectToNumberStrategy(YAML_EQUIVALENT_NUMBER_STRATEGY)
.setLenient();
return builderModifier.apply(base).create();
}
@ -95,4 +146,19 @@ public class Json {
registerTypeAdapterFactory(ThrowableAdapter.FACTORY);
}
/*public static void main(String[] args) {
TypeToken<Map<String, Object>> MAP_STR_OBJ_TYPE = new TypeToken<>() { };
Map<String, Object> map = gson.fromJson("{" +
"\"int\":34," +
"\"long\":3272567356876864," +
"\"bigint\":-737868677777837833757846576245765," +
"\"float\":34.0" +
"}", MAP_STR_OBJ_TYPE.getType());
for (String key : map.keySet()) {
Object v = map.get(key);
System.out.println(key + ": " + v + " (type " + v.getClass() + ")");
}
}*/
}

View File

@ -10,13 +10,11 @@ import com.google.gson.JsonSerializer;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.bind.TreeTypeAdapter;
import com.google.gson.reflect.TypeToken;
import fr.pandacube.lib.core.json.Json;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.BlockVector;
import org.yaml.snakeyaml.Yaml;
import java.lang.reflect.Type;
@ -68,12 +66,12 @@ import java.util.Map;
}
public static void main(String[] args) {
/*public static void main(String[] args) {
PaperJson.init();
BlockVector bv = new BlockVector(12, 24, 48);
String json = Json.gson.toJson(bv);
System.out.println(json);
BlockVector bv2 = Json.gson.fromJson(json, BlockVector.class);
System.out.println(bv.equals(bv2));
}
}*/
}