Need some assistance with Serialization/Deserialization of configs

Hey all,

I’ve been trying to implement a configuration load/write/create subroutine for my mod. I’ve had some trouble understanding the docs, but I tried to do as much as I can.

My questions are:

  1. How do I get the exception IO catch to create and fill a defaults file if it determines that no file yet exists?
  2. What exactly does a node value mean? is it how you access the specific node in the node object/name of the node or is it the value given to that node (like int x = 1?).
  3. if I’m serializing a complex object (an object that contains arrays, Lists, enums, and custom objects), do I have to do the @Inject, @ConfigSerializable, and @Setting for each object within the serialized object (excluding primitives)?
  4. Are there any red flags you can see in how I’ve written the code so far? The information I need serialized and accessible to users doesn’t lend itself well to bare-bones tutorials i’ve found (in general).

Also I put in a lot of effort formatting this question, but if changing the formatting will make it more readable, let me know.

So far this is what I have:

    	@Plugin(id = "SomeName", name = "Some Longer Name", version = "1.0a")
    	public class SomeNameMain {

            //The Variables
		@Inject 
		@DefaultConfig(sharedRoot = true) 
		private File defaultConfig;
		@Inject
		@DefaultConfig(sharedRoot = true)
		private ConfigurationLoader<CommentedConfigurationNode> configManager;
	
		@Inject private GuiceObjectMapperFactory factory;
		@Inject private ConfigurationLoader<CommentedConfigurationNode> loader
 		private Defaults newDefaults;

		@Subscribe
		public void enable(GamePreInitializationEvent event) {
			File potentialFile = new File("config.conf");
			ConfigurationLoader<CommentedConfigurationNode> loader =
			HoconConfigurationLoader.builder().setFile(potentialFile).build();
			CommentedConfigurationNode node;
			try {
				//Attempt to load object 'Defaults' from file. If successful, populate newDefaults
				//Object. 
				node =loader.load(ConfigurationOptions.defaults().setObjectMapperFactory(factory));
				newDefaults = node.getValue(TypeToken.of(Defaults.class));
			} catch(IOException e) {
				//If loading can't be done (i.e. file doesn't exist):
				//1. Create empty node
				//2. Create Defaults object with a special constructor that tells it to populate w/ default values
				//3. Assign this new Defaults object to the node tree
				//4.Save node to directory.
				node = loader.createEmptyNode(ConfigurationOptions.defaults());
				newDefaults = new Defaults(false);
				node.setValue(newDefaults);
			}
		}
	} 

The Defaults class is:

    @ConfigSerializable
    public class Defaults {

    	@Setting(value="size", comment="The Size of This Object")
    	private int size; 
    	@Setting(value="ages", comment="The Number of Ages")
    	private int chrons;
    	//Not sure how to do this:
    	@Setting(value="notsurethevalue", comment="The object List")
    	//private List<CustomComplexObject> chronval;

    	@Inject
    	public Defaults(){
    		// Will it auto fill the fields? or do I have to do that
    		//with node.etc;?
    	}
    	public Defaults(Boolean populate) {
    		if(populate){
    			fillDefaults();
    		}
    	}
    	private void fillDefaults()  {
    	//fill defaults
    	}
     }

2: A node value is the data that’s being stored (like 1, “foo”, true).
4: Your indentation makes the code a bit hard to read. Try formatting your code.

Not sure about 1 and 3, though.

would you prefer a:

 public void method() {
     // stuff
}

Indentation style?

The standard formatting convention is something like this (code only for example purposes):

@Plugin(id = "myplugin", name = "MyPlugin")
public class MyPlugin {
    @Inject
    private Game game;

    @Listener
    public void onInitialization(GameInitializationEvent event) {
        int x = 2;
        int y = 4;
        System.out.println(x + y);
        for (int i = 0; i < 10; i++) {
            int j = i * 2;
            System.out.println(i + j);
        }
    }
}

Just make sure that lines with the same level have the same amount of whitespace to the left. It’s also a good idea to be consistent between tabs and spaces, since some places make tabs 4 spaces wide and some places make them 8 spaces wide.

Some other notes:

  • A constructor does not need to be marked with @Inject if it doesn’t take any parameters.
  • With
    //Not sure how to do this:
    @Setting(value="notsurethevalue", comment="The object List")
    private List<CustomComplexObject> chronval;

value means the name of the key (similar to x in int x = 0;). It can be omitted; the default is the name of the field (chronval in your case).

Formatting:

So to clarify, primitive values are blue, strings are bright red, private/void/Class/for/primitive declarations bold, and methodNames dark red? I’ll try to clean up my original post.

I’m unable to put tabs by pressing tab for some reason, so It’s pretty hard to differentiate between tabs and whitespace on my browser. I’ll check in a moment on Chrome and see if there’s a difference. Anywhoo…

Coding:

Ok, so lets pretend CustomComplexObject has other primitive and complex fields. Do I also have to put @Setting into them?

EDIT: I’m dumb. So apparently I have no idea how to do the nice color & font formatting for code, I’ve just been clicking the </> after selecting my copy-pasted code. How do you beautify code in the sponge forums?

For the formatting, I was talking about whitespace, not colors. The colors is Java syntax highlighting - after the three back ticks, put java. As in:

```java

For entering tabs in the browser, you can only do it via copy-paste.

If CustomComplexObject has sub-fields, you still need to add @Setting to them for them to be saved.

Thanks @JBYoshi. That answers most of #3 and you’ve already answered #2. I still can’t get the '''java to work out though. I edited my attempt in.

Use this: Markdown syntax highlighting · GitHub

Done! thxs for that.

For anyone else still reading this thread, I still am not sure on point #1 and the List < Custom Object> part for #4.

Edit: I’m also getting an error on this line:

     newDefaults = node.setValue(TypeToken.of(Defaults.class));

Error is “Method of( Class< Defaults>) is not defined for the type TypeToken”

  1. In theory configurate does that validation for you and will just return an empty node if the file does not exist. Unless you check the File object itself you have no way of determining if the file does exist.

  2. A nodes value is the value held by this node, or, as you put it, the value given to it.

  3. Every field annotated with @Setting needs to be of a serializable type.

A type is serializable if any of the following are true:

  • It is a primitive type or its boxed class (Integer, Double, Boolean, …)
  • It is a String, UUID, URL, URI or a regex Pattern
  • It is an Enum
  • It is a List or a Map of serializable Types
  • You did provide a custom TypeSerializer for that type in the ConfigurationOptions
  • The type is annotated with @ConfigSerializable and every of its fields that is annotated with @Setting is serializable

That means if you have a class


    @ConfigSerializable
    public class PlayerHomes {
        @Setting("player-uuid")
        private UUID playerId;
        @Setting("homes")
        private List<Home> homes;

        ...
    }

you will need to make sure the Home class is serializable by either annotating it with @ConfigSerializable or providing a TypeSerializer in your ConfigurationOptions.

If I understood the code correctly, you want to read to read a value from the config file and use a default value if it is not there. Also, if the config was empty, you want the default value to be written to it. I recommend doing it this way (the following replacing your try/catch block)

    final ConfigurationOptions options = ConfigurationOptions.defaults()
                                            .setObjectMapperFactory(factory)
                                            .setShouldCopyDefaults(true);    // (a)

    try {
         node = loader.load(options);
    }
    catch(IOException ex) {
         // Severe Error - you should at least log a message and the exception here
         node = loader.createEmptyNode(options);
    }
    newDefaults = node.getValue(TypeToken.of(Defaults.class), new Defaults(true)); // (b)

You’ll notice a couple of changes. First, the ConfigurationOptions - i have extracted them into a local variable and added the line I marked with (a).

Second, the try/catch block now only deals with the loading. It is made sure that no matter what I got a CommentedConfigurationNode after, be it one loaded from the file or created in case of a serious error while reading. Again, a missing file will not lead to an exception, as configurate will just treat a missing config file as empty.

Third, there is now only one line dealing with reading the value from the configuration node, which I marked with (b). This line uses an overloaded getValue method that accepts not only the TypeToken (which is needed to determine the appropriate deserializer) but also a default value. If there is no proper value present on the node, this default value will be returned by the method.
Since on line (a) we told configurate that we would like it to copy defaults, if the default value was used, it will also be set to the node. So if we have an empty configuration node and the line (b) is ran on it, not only will it return to us the value created by new Defaults(true), but also will that value be serialized to the config node.

I hope I could help you with your problems. Be aware that while I have consulted the appropriate javadocs, I have tested none of the above code. Feel free to tell me what exactly your problems with the existing configuration docs were so that I may incorporate them into my rework. Preferably on github, but if you do not have (and do not want) a github account, our Docs Forum Category is fine too.

1 Like

Looks like you’re using apostrophes before java, and not the downwards pointing accents. Also remember to start the code block on a new line, and linebreak right after “java”.

```java
Public Static IHaveNoIdeaWhatIAmDoing(String s){
    // Please send help
}
\```

The backslash is for visibility only :stuck_out_tongue:

Edit: Never mind. I scroll up to the first post tosee your attempt, and there it is :slight_smile:

@Lemonous
Your profile icon creeps me out, so good job there! ^_^. Also nice use of meme convention in your method name.

@Saladoc
Thanks! That explained most of it to me!

The thing in the docs that was confusing me the most was the use of terms ‘serialization’ and ‘deserialization’; When I’ve heard these terms before they usually referred to reading/writing to the file system, but it seems like with configs it refers to parsing it to the node tree object?

I also implemented your suggestions. However I’m getting two errors:

     .setShouldCopyDefaults(true);

‘The Method .setShouldCopyDefaults(true) is undefined for the type configurationOptions’

I’m assuming this is because I need to implement .setShouldCopyDefaults? but the dot syntax indicates it’s a method of the configurationOptions object, and thus its class file is not defined by me.

and

     newDefaults = node.getValue(TypeToken.of(Defaults.class), new Defaults(true)); // (b)

The Method ‘of( Class< Defaults > )’ is undefined for the type ‘TypeToken’

I would assume this comes up if the corresponding ‘Defaults’ class is lacking in the proper @ annotations. Triple checking assures me it is still identical to the Defaults class defined above, minus the question annotation.

I’d be happy to give feedback in the Docs Forum Category; I’m still trying to learn the dizzying world of github atm, so perhaps its best not to use it until I have a better understanding of how it works.

That is correct. Directly setting values is only supported for those values that are natively handled by the underlying implementation of the file format. All others have to be serialized to a ConfigurationNode based structure first.

No. Both this and your other issue with TypeToken seem to be pointing at outdated versions of configurate / guava being shaded into your SpongeAPI jar. If you manually copied a SpongeAPI.jar into your workspace, make sure to update it and see if the problem still occurs. If you use a build management tool like gradle or maven, force it to update the dependencies and see if the problem persists.

This particular project was made with the maven tutorials a ways back. Upon doing the maven> update project> click ‘force update snapshots/releases’, and update… nothing happens :S . the TypeToken.to still shows the same error.

This seems to be hinting at some obscure problem with your local setup which I sadly can only offer the most generic support for: Check if your everything is up to date. Especially maven.
If you wish, you can also upload your project somewhere (github preferably) and I’ll try it on my machine seeing if I can reproduce your error.

You’re right. Did a clean install and everything seems to be fine with the world once more.

However, upon exporting and running, no config file seems to be generated.

Here’s the function as it stands now:

      @Listener
     public void enable(GamePreInitializationEvent event)
     {
	File potentialFile = new File("config.conf");
	ConfigurationLoader<CommentedConfigurationNode> loader = 
			HoconConfigurationLoader.builder().setFile(potentialFile).build();
	final ConfigurationOptions options = ConfigurationOptions.defaults()
			.setObjectMapperFactory(factory).setShouldCopyDefaults(true);
	CommentedConfigurationNode node;
	try{
	    node = loader.load(options);
	}
	catch(IOException ex){
	    node = loader.createEmptyNode(options);
	}
	try {
    	    defaults = node.getValue(TypeToken.of(Defaults.class), new Defaults(true));
    	} catch (ObjectMappingException e) {
    	     e.printStackTrace();
    	}
    }