From a24eab67b6eaa186643d844e0896a8aaf8a5ab52 Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Sun, 8 Oct 2023 23:57:46 +0200 Subject: [PATCH] Gson now deserializes numbers to the appropriate Number subclass --- .../java/fr/pandacube/lib/core/json/Json.java | 66 +++++++++++++++++++ .../ConfigurationSerializableAdapter.java | 6 +- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/json/Json.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/json/Json.java index e4ff7bb..ef19a40 100644 --- a/pandalib-core/src/main/java/fr/pandacube/lib/core/json/Json.java +++ b/pandalib-core/src/main/java/fr/pandacube/lib/core/json/Json.java @@ -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_STR_OBJ_TYPE = new TypeToken<>() { }; + Map 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() + ")"); + } + }*/ + } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/ConfigurationSerializableAdapter.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/ConfigurationSerializableAdapter.java index 376e805..2577b55 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/ConfigurationSerializableAdapter.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/ConfigurationSerializableAdapter.java @@ -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)); - } + }*/ }