Open xxDark opened 3 years ago
Yes that is possible by using InstanceCreator
, which can be registered using GsonBuilder.registerTypeAdapter(...)
.
Though it depends on your usecase how useful this will be for you. Also be careful to not share the Gson
instance with other threads, and ideally create a new one for every deserialization (to avoid multiple calls changing each others existing instance).
Here is an example:
private static class MyClass {
public String f;
public int f2;
@Override
public String toString() {
return "f=" + f + ", f2=" + f2;
}
}
public static void main(String[] args) {
MyClass existingInstance = new MyClass();
existingInstance.f = "test";
Gson gson = new GsonBuilder()
// Could also create anonymous InstanceCreator subclass instead of using lambda
.registerTypeAdapter(MyClass.class, (InstanceCreator<?>) type -> existingInstance)
.create();
MyClass deserialized = gson.fromJson("{\"f2\":123}", MyClass.class);
System.out.println("Is same instance: " + (existingInstance == deserialized));
System.out.println(deserialized);
}
create a new one for every deserialization (to avoid multiple calls changing each others existing instance).
Creating Gson
instances for each deserialization is not cheap, because it does heavy stuff under hood.
My first idea was actually instance creator, but I thought it is a hack.
I guess there is no other way, right?
Depending on how complex the object structures you are deserializing are, creation of new Gson
objects would indeed not be cheap (since you could not take advantage of its type adapter cache). What you could try is using a static Gson
instance and then having an InstanceCreator
subclass which stores the instance in a ThreadLocal
field. Then before you deserialize your object you would access that instance creator and change its ThreadLocal
value. This is somewhat hacky, but it would probably work. However, you need to be careful when using recursion to prevent accidentially replacing the ThreadLocal
value in a recurisve call and then later in the original call assume that it still has its old value.
Could you share a little bit more information about your usecase? Then it might be easier to give more specific answers.
I have a websocket connection between client and server (Netty used), so I need to decode a lot of messages fast. Perhaps I could create Gson
instance for each event loop, not for each connection or packet.
I have a similar use case. I have a Map of strong references to objects in the code that are intended to be configurable via Gson.
When saving them, I turn them into JsonObjects and push them to one large JsonArray, then write that array to a file.
When loading them, I parse the file into a JsonArray, then parse each element as an Object and try to match load (Object key = Map key.toString()).
Although I tried the InstanceCreator tip above, it doesn't seem to actually "manipulate" the object. I need data from the config written into the object, but it doesn't seem to.
private static final Map<Class<? extends AbstractTweak>, AbstractTweak> tweaks = new HashMap<>(); // TODO: memory implications with high object counts
private static final Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().serializeNulls().setPrettyPrinting().registerTypeAdapter(AbstractTweak.class, (InstanceCreator<AbstractTweak>) tweaks::get).create();
private static final Path config = FabricLoader.getInstance().getConfigDir().resolve("infinity.json");
static {
tweaks.put(Tweak1.class, new Tweak1());
tweaks.put(Tweak2.class, new Tweak2());
// loading the config, please dont mind this awful mess
tweaks.forEach((k, v) -> {
jsonArray.forEach(x -> {
if (x.getAsJsonObject().get(k.getSimpleName()) != null) {
gson.fromJson(x, k); // expected this to modify the item in-place
}
});
});
}
unless im misunderstanding what i should be doing, this doesn't work. instead, what it seems to do is just return that object that I defined at the type adapter. How would I tell json to manipulate that object once its gotten it? The fields aren't final or anything like that either, just private.
@Frontear, where is the jsonArray
variable in your code coming from? Also, with the code you have shown it looks like it would be easier to just deserialize the Tweak1
and Tweak2
objects as usual with Gson and put them in the tweaks
map afterwards instead of trying to do any modifications on existing objects.
This whole workaround of using InstanceCreator
to modify existing objects is quite brittle and breaks thread-safety of Gson. For example with your code accessing tweaks
or gson
outside the static
block is most likely not thread-safe unless you add additional synchronization.
I actually rewrote my logic, but to add explanation:
jsonArray
came from Gson.fromJson(file_reader, JsonArray.class)
.
Instead of what I was doing above, I opted to first set the map with null objects, let Gson instantiate and set them first, and if Gson couldn't for whatever reason, it would instantiate the null values only later on.
Hello, I wonder if it is possible (without hacking gson library internals) to load data into existing ojects without constructing new ones. Thanks for assistance.