OkaeriPoland / okaeri-configs

Simple Java/POJO config library written with love and Lombok
MIT License
83 stars 11 forks source link

failed to #getValue for crates and Cannot cast me.paradis.smashcratess.crates.actions.MessageAction to java.util.List #44

Closed Paradis4432 closed 1 year ago

Paradis4432 commented 1 year ago

really nice framework, i use it for all my projects but this time i tried to do something a little bit more complex

Describe the bug registered serializer for class but its not able to find it, or get a value? Caused by: eu.okaeri.configs.exception.OkaeriException: failed to #getValue for crates

To Reproduce Steps to reproduce the behavior: tried to follow the LocationSerializer under SerdesBukkit

  1. created class
  2. created serializer
  3. registered it but i get failed to get value

Expected behavior MessageActionSerializer to override serialize method and save my custom value

Library version implementation 'eu.okaeri:okaeri-configs-yaml-bukkit:5.0.0-beta.5' implementation 'eu.okaeri:okaeri-configs-serdes-bukkit:5.0.0-beta.5'

Config classes and setup routine all parts: https://paste.md-5.net/vowasuceki.java full error: https://paste.md-5.net/osiqufiluc.bash

thank you 😄

Paradis4432 commented 1 year ago

would it be possible to create one serializer and make the different action handlers (like MessageAction) override this method to aovid having a single class for each action handler (MessageActionSerializer)

dasavick commented 1 year ago

Your example is missing the config POJO itself, which probably reveals some unsuitable field type, but my best guess is that you are attempting to create a collection where the contents are all concrete implementations of a single interface.

After the object is transformed into the YAML data using okaeri-configs, the information about the type is lost. The only source of the type truth is the config definition class itself. This means that while it may be possible to serialize such collection, it will not be possible to deserialize it without including additional data, such as field 'type' with an enum or enum-like type.

For one of the solutions, you would need to create a field List<MyInterfaceType>, register a serializer for only the MyInterfaceType, and then implement MyInterfaceType serializer to pass the data to specific serializers or deserialize it in place, all while routing based on the previously mentioned type field.

Example of such a solution, where the type field is represented as action:


public class MyActionSerializer implements ObjectSerializer<MyAction> {

    @Override
    public boolean supports(@NonNull Class<? super MyAction> type) {
        return MyAction.class.isAssignableFrom(type);
    }

    @Override
    public void serialize(@NonNull MyAction action, @NonNull SerializationData data, @NonNull GenericsDeclaration generics) {
        data.add("action", action.getAction());
        switch (action.getAction()) {
            case ACTION_ONE -> {
                data.add("field1", ((MyActionOne) action).getFieldA1(), SomeType1.class);
                data.addCollection("field2", ((MyActionOne) action).getFieldA2(), SomeType2.class);
            }
            case ACTION_TWO -> {
                data.add("field1", ((MyActionTwo) action).getFieldB1(), SomeType3.class);
            }
        }
    }

    @Override
    public MyAction deserialize(@NonNull DeserializationData data, @NonNull GenericsDeclaration generics) {
        MyActionType action = data.get("action", MyActionType.class);
        return (switch (action) {
            case ACTION_ONE -> new MyActionOne(
                data.get("field", SomeType1.class),
                data.getAsList("field2", SomeType2.class)
            );
            case ACTION_TWO -> new MyActionTwo(
                data.get("field1", SomeType3.class)
            );
            case NOOP -> new NoopAction();
        });
    }

    /** FIXME: remove if using auto-registration with ConfigSerializable, the better way to serialize first-party types
    public static class Serdes implements OkaeriSerdesPack {

        @Override
        public void register(@NonNull SerdesRegistry registry) {
            registry.register(new MyActionSerializer());
        }
    }
    **/
}
@Data
public abstract class MyAction implements ConfigSerializable {

    private static final MyActionSerializer MY_ACTION_SERIALIZER= new MyActionSerializer();
    private final MyActionType action;

    public abstract void execute(@NonNull MyContext context);

    @Override
    public void serialize(@NonNull SerializationData data, @NonNull GenericsDeclaration generics) {
        MY_ACTION_SERIALIZER.serialize(this, data, generics);
    }

    public static MyAction deserialize(@NonNull DeserializationData data, @NonNull GenericsDeclaration generics) {
        return MY_ACTION_SERIALIZER.deserialize(data, generics);
    }
}
@Data
public class MyObject extends OkaeriConfig {
    // ...
    private List<MyAction> actions = new ArrayList<>();
    // ...
}
Paradis4432 commented 1 year ago

what a beautiful person you are!

worked like a charm thank you

for those in the future this is the updated code: https://paste.md-5.net/safusuhebi.java