According to the docs, I don’t need to do any other registration with Configurate if I’m using the @ConfigSerializable and @Setting annotations, right?
Caused by: java.lang.IllegalArgumentException: Configuration does not accept objects of type class me.desht.testplugin.MyObject
Am I missing something basic here? I have also provided an empty constructor in the MyObject class, as the docs mentioned. I haven’t tried using a custom ObjectMapperFactory yet.
What is the visibility of the empty constructor? I’d also suggest trying to use the GuiceObjectMapperFactory and see if there is any behaviour differences.
The empty constructor is of public visibility. I also tried adding a GuiceObjectMapperFactory, but that didn’t help - same result.
Update: I also tried an explicit serializer class for my test objects (as documented in the official docs), and I still get the same error. So either I’m completely misunderstanding how object serialization is supposed to work (but the docs seem clear enough), or the whole object serialization system is broken.
You did not make any mistakes. The problem stems from the HoconConfigurationLoader::createEmptyNode(ConfigurationOptions) function, as it overwrites the set of accepted types passed in with the ConfigurationOptions. Per default, this set is null, meaning (if I understood it correctly) that any type that can be serialized is allowed.
However, all implementations but the YAMLConfigurationLoader, which was the one I used for testing when I wrote the docs page you mentioned, overwritethe allowedtypes which leads to any type not being Map, List, Double, Long, Integer, Boolean, String or Number being rejected, no matter the serializers.
For now, I’ll recommend you switch your config format and use YAMLConfigurationLoader (at least to verify that your code indeed works). I’ll try to contact @zml in order to resolve this problem and make sure the updated configurate files are included in sponge as soon as possible.
Edit: Sorry for my initial, misguided answer. Of course zml is right, it is the wrong method. using node.getObject("the_object").setValue(TypeToken.of(MyObject.class), obj); works just fine.
I’ve looked at the configuration docs and noticed there is no explanation on those different methods, so your mistake is understandable. I’ll go ahead and rework those pages in the next days.
setValue doesn’t perform any sort of serialization. iirc you have to use the setValue method that accepts a TypeToken for any sort of type serialization to be performed.
Perhaps it might make sense to move the current setValue(obj) to setRawValue(obj) or something and make a new setValue(obj) guess at the appropriate type?
Thank you! Using the TypeToken version of setValue() does indeed do the trick.
Another question, though, regarding object mappers: the docs imply that supplying an empty constructor isn’t necessary if a custom mapper factory (e.g. GuiceObjectMapperFactory) is used, but I’m not totally clear on what I need to do there.
I suppose the key here is the statement in the docs: “any class that guice can create via dependency injection”. Not being too familiar with Guice, what do I need to do to ensure my classes can all be created via dependency injection? Is it just a case of annotating any serializable fields with @Inject?
The default object mapper requires a no-args constructor in order to work, since it first uses that constructor to initialize the object and then reflection to set the fields annotated with @Setting.
If you provide a different ObjectMapperFactory, it depends on the factory under which condition the mapper is able to instantiate new objects. For the GuiceObjectMapperFactory, the condition is: “The Object can be created by guice”. So the guice object mapper will first use the supplied Injector to create the class, then again use reflection to set the annotated fields.
In order to be creatable by guice, your class has to have a public no-args constructor or a constructor annotated with @Inject. In the latter case, guice will try to provide values for every of that constructors parameters and raise an error if it failed.
Your serialized fields do not need any further annotations. Those are taken care of by the mapper. As an example, I assume that your MyObject class also needs a reference to the Game object in the constructor for whatever reason, but not as a field.
@ConfigSerializable
public class MyObject {
@Setting
private String name;
@Setting
private String title;
@Setting
private UUID ownerID;
@Inject
public MyObject(Game game) {
// use game to create more FLARD
}
// ...methods etc...
}
This way, there is no no-args constructor, so the default ObjectMapper would fail to create instances of that class. However, there is a constructor annotated with @Inject whose parameters can be provided by the Injector. Therefore guice will be able to instanciate your class and afterwards the object mapper will set the annotated fields.
I’ve been getting a little further into serialization, and I’ve hit another problem. I’ve created a very simple TestClass class and also a TestClassSerializer class to handle serialization (for this example, I’m not using the @ConfigSerializable annotation). I’ve registered the serializer:
TypeSerializers.getDefaultSerializers().registerType(TypeToken.of(TestClass.class), new TestClass.TestClassSerializer());
and then:
ConfigurationLoader<CommentedConfigurationNode> loader = HoconConfigurationLoader.builder().setFile(new File("itemtest")).build();
ConfigurationNode node = loader.createEmptyNode(ConfigurationOptions.defaults());
TestClass tc1 = new TestClass("name", "title", UUID.randomUUID());
List<TestClass> list = Arrays.asList(tc1);
try {
// this works fine
node.getNode("tc1").setValue(TypeToken.of(TestClass.class), tc1);
// this doesn't!
node.getNode("tc1-list").setValue(list);
loader.save(node);
} catch (ObjectMappingException | IOException e) {
e.printStackTrace();
}
I can serialize objects of my custom class just fine, everything works. But if I try to serialize a list of objects of my custom class, I get an exception:
Caused by: java.lang.IllegalArgumentException: Configuration does not accept objects of type class me.desht.testplugin.TestClass
So how do I tell Configurate that the list I’m passing contains objects of my custom class? I can’t pass a TypeToken.of(TestClass.class) when I pass a list in to setValue().
It needs to be a TypeToken representing List<TestClass>. The recommended way of solving this is creating an anonymous class new TypeToken<List<TestClass>>() {}. This only works if you know what kind of List you’re dealing with during compile-time.