0
votes

I can't for the life of me figure out how deserialize this:

{
  "c8147c8a-09c3-4165-b5c2-ce72e2c97100": {
    "pets": {
      "BOOST": [
        {
          "mcmmoBoost": 15.0,
          "owner": "c8147c8a-09c3-4165-b5c2-ce72e2c97100",
          "entityType": "IRON_GOLEM",
          "health": 150.0,
          "tier": 1,
          "alive": true
        }
      ]
    },
    "uuid": "c8147c8a-09c3-4165-b5c2-ce72e2c97100"
  }
}

into a Map<UUID, PetPlayer> with PetPlayer containing a multimap called "pets" structured as follows; Multimap<PetType, Pet>. PetType is an enum here and Pet is an abstract class that has multiple implementations.

I tried using these two serializers and deserializers.

First: public final class HashMultimapAdapter implements JsonSerializer>, JsonDeserializer> { private static final PetAdapter petAdapter = new PetAdapter();

    @Override
    public JsonElement serialize(Multimap<PetType, Pet> src, Type typeOfSrc, JsonSerializationContext context) {
        return context.serialize(src.asMap());
    }

    @Override
    public Multimap<PetType, Pet> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        Map<PetType, Collection<JsonElement>> asMap = context.deserialize(json, new TypeToken<Map<PetType, Collection<JsonElement>>>(){{}}.getType());
        Multimap<PetType, Pet> multimap = ArrayListMultimap.create();

        for (Map.Entry<PetType, Collection<JsonElement>> entry : asMap.entrySet()) {
            entry.getValue().forEach(jsonElement -> {
                multimap.put(entry.getKey(), petAdapter.deserialize(jsonElement, Pet.class, context));
            });
        }

        return multimap;
    }
}

Second:

public class PetAdapter implements JsonSerializer<Pet>, JsonDeserializer<Pet> {
    @Override
    public Pet deserialize(JsonElement jsonElement, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        EntityType entityType = EntityType.valueOf(jsonElement.getAsJsonObject().get("entityType").getAsString());

        switch (entityType) {
            case IRON_GOLEM:
                return context.deserialize(jsonElement, EcoPet.class);
            case WOLF:
                return context.deserialize(jsonElement, BoostPet.class);
            case MAGMA_CUBE:
                return context.deserialize(jsonElement, CombatPet.class);
            default:
                throw new JsonParseException("Invalid PetType");

        }
    }

    @Override
    public JsonElement serialize(Pet src, Type typeOfSrc, JsonSerializationContext context) {
        return context.serialize(src);
    }
}

This resulted in a stackoverflow.

java.lang.StackOverflowError at com.google.gson.internal.$Gson$Types.resolve($Gson$Types.java:375) ~[PaperSpigot-1.8.8-R0.1.jar:git-PaperSpigot-"4c7641d"]

I greatly appreciate any help :)

1

1 Answers

0
votes

I don't really know a way to do this with gson without it being manually or hacky. This is too big to put as a comment, so I'll leave it here as an answer as an idea to help you out.

First, you get a stack overflow because you are calling context.deserialize on the same parameters which triggers gson to call the same deserializer, which will call again context.deserialize and so on, until the stack overflow.

You'll run into the same problem when serializing because you're also just doing context.serialize.

To avoid this, you'll need to avoid that gson recurses into calling the serializer's/deserializer's methods. This is very easy to achieve by creating another gson instance without the adapters:

public class PetAdapter 
           implements JsonSerializer<Pet>, JsonDeserializer<Pet> {
  private final Gson gson = new Gson();

  @Override
  public Pet deserialize(JsonElement jsonElement, Type typeOfT, 
      JsonDeserializationContext context) throws JsonParseException {
     EntityType entityType = EntityType.valueOf(jsonElement.getAsJsonObject().get("entityType").getAsString());

      switch (entityType) {
        case IRON_GOLEM:
            return gson.fromJson(jsonElement, EcoPet.class);
        case WOLF:
            return gson.fromJson(jsonElement, BoostPet.class);
        case MAGMA_CUBE:
            return gson.fromJson(jsonElement, CombatPet.class);
        default:
            throw new JsonParseException("Invalid PetType");
      }
  }

  @Override
  public JsonElement serialize(Pet src, Type typeOfSrc, JsonSerializationContext context) {
    return gson.toJson(src);
  }
}

This works, but only if your Pet implementations do not depend on other custom serializers/deserializers. So as you can imagine this is quite hacky.

Another approach is the manual deserialization. This means that you'd have to go through json element and read the properties like you are reading entityType and manually build your objects.

Very similarly, I guess (I didn't check this), you could first use context to deserialize each pet into a Map of objects and let each pet implement a static method that creates an instance of the specific pet from this map. Something like:

public class IronGolem extends Pet {
   public static IronGolem from(Map<String, Object> deserializedPet) {
      // here check the map for each thing you need
      return new IronGolem(/*pass in every attribute*/);
   }
}

Hope this helps.