PandaLib/Core/src/main/java/fr/pandacube/lib/core/util/Json.java

105 lines
3.5 KiB
Java

package fr.pandacube.lib.core.util;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.RecordComponent;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
public class Json {
public static final Gson gson = build(b -> b);
public static final Gson gsonPrettyPrinting = build(b -> b.setPrettyPrinting());
public static final Gson gsonSerializeNulls = build(b -> b.serializeNulls());
public static final Gson gsonSerializeNullsPrettyPrinting = build(b -> b.serializeNulls().setPrettyPrinting());
private static Gson build(Function<GsonBuilder, GsonBuilder> builderModifier) {
return builderModifier
.apply(new GsonBuilder().registerTypeAdapterFactory(new RecordAdapterFactory()).setLenient()).create();
}
// from https://github.com/google/gson/issues/1794#issuecomment-812964421
private static class RecordAdapterFactory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) type.getRawType();
if (!clazz.isRecord() || clazz == Record.class) {
return null;
}
return new RecordTypeAdapter<>(gson, this, type);
}
}
private static class RecordTypeAdapter<T> extends TypeAdapter<T> {
private Gson gson;
private TypeAdapterFactory factory;
private TypeToken<T> type;
public RecordTypeAdapter(Gson gson, TypeAdapterFactory factory, TypeToken<T> type) {
this.gson = gson;
this.factory = factory;
this.type = type;
}
@Override
public void write(JsonWriter out, T value) throws IOException {
gson.getDelegateAdapter(factory, type).write(out, value);
}
@Override
public T read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
} else {
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) type.getRawType();
RecordComponent[] recordComponents = clazz.getRecordComponents();
Map<String, TypeToken<?>> typeMap = new HashMap<>();
for (int i = 0; i < recordComponents.length; i++) {
typeMap.put(recordComponents[i].getName(), TypeToken.get(recordComponents[i].getGenericType()));
}
var argsMap = new HashMap<String, Object>();
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
argsMap.put(name, gson.getAdapter(typeMap.get(name)).read(reader));
}
reader.endObject();
var argTypes = new Class<?>[recordComponents.length];
var args = new Object[recordComponents.length];
for (int i = 0; i < recordComponents.length; i++) {
argTypes[i] = recordComponents[i].getType();
args[i] = argsMap.get(recordComponents[i].getName());
}
Constructor<T> constructor;
try {
constructor = clazz.getDeclaredConstructor(argTypes);
constructor.setAccessible(true);
return constructor.newInstance(args);
} catch (NoSuchMethodException | InstantiationException | SecurityException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
}
}