Need help reading configuration file (map)

I have spent too many hours trying to code the configuration part of one plugin, time to ask for help…

Here is the config file I have, truncated at just 5 entries and names changed, but otherwise the same structure…

ConfigVersion=1
sample {
    alpha=4.25
    beta=3.75
    gamma=3.75
    delta=5.75
    epsilon=4.25    
}

When I load this config file ( this.config = getConfigManager().load(); approach)

I can’t get the list (actually a map…) from the sample node
When I get the sample node, the hasListChildren() is false, while hasMapChildren() is true, so thats a good sign…
But, when I get the map, it says the map size is 2, and I can’t iterate through it to see what it is anyways, but it should be 5 (well, 18 , but definitely not 2)

What do I need to do to read these from the file. My ultimate structure for the data loaded in is going to be a Map <String>, <Double> so there should be a way to read everything directly into a map, and if not, a way to get an interatable collection of something from that node that I can then go through and shove the key,value pairs into my map, but I’m burned out by not getting anywhere and really not understanding the config stuff despite many iterations of the documentation and old postings here :frowning:

public void loadConfig(){  
    // need to populate map object  this.alphasettings, a string,double hashmap.
     this.config = getConfigManager().load();
    // What goes here?
}

Hey,

So first off, I just woke up, and my guesses solution to this issue could very well be wrong. I’m just taking a guess based off the symptoms.

You said that when you try to get the map from the config file (or when you do the size is 2.
My guess is that the Map is actually String to Object or something similar, and it is returning a map with the value [ConfigVersion=1, sample=Map] or something along those lines.

I am pretty sure that the configuration reader does not just automagically turn a multi level config file into a single level map. At the very least, if I was making a configuration manager it wouldn’t really work like that, without some other boilerplate.

Have you tried iterating over the map, and printing the set? If so what is the output.

Map<Object, ? extends ConfigurationNode> mapResult = config.getNode("sample").getChildrenMap();

is where my tired mind is last able to get the map from it, but I can’t figure out how to iterate it , my ide keeps complaining about the different approaches I take as can-only-iterate-over-iteratable, or else I do something like this

            Map<Object, ? extends ConfigurationNode> mapResult = config.getNode("sample").getChildrenMap();
            for (Object key: mapResult.keySet()) {
                System.out.println(key + " :: " + mapResult.getValue(key));
            } 

But the ide cries about getValue(key) as The method getValue(Object) is undefined for the type Map<Object,capture#3-of ? extends ConfigurationNode> so not being able to figure out how to dig through things to see whats going on, I’m feeling really limited.

Try something like this:

    for (Map.Entry<Object, ? extends ConfigurationNode> entry : mapResult.entrySet()) {
        System.out.println(entry.getKey() + " :: " + entry.getValue());
    }

see if this works.

If you want to use your way of doing it, replace Map#getValue with Map#get

A functional and working example would be in this method here.

I dont know what you mean about change map#getValue to map#get … the only similarity i see is entry.getValue() but if you change that to entry.get() then the ide barfs up errors again about get() being undefined for the type blah…blah…

The good news is that yes, that did iterate through the keys, but still stuck on the values right now, though I could workaround that by actually looking up the node with that key value, otherwise, this is the output it generates (repeat this style for the 18 others I have…)

[12:17:56 INFO] [STDOUT]: alpha :: SimpleCommentedConfigurationNode{super=SimpleConfigurationNode{options=ninja.leaping.configurate.ConfigurationOptions@e5478e38, attached=true, key=alpha, parent=SimpleCommentedConfigurationNode{super=SimpleConfigurationNode{options=ninja.leaping.configurate.ConfigurationOptions@e5478e38, attached=true, key=sample, parent=SimpleCommentedConfigurationNode{super=SimpleConfigurationNode{options=ninja.leaping.configurate.ConfigurationOptions@e5478e38, attached=true, key=null, parent=null, value=ninja.leaping.configurate.MapConfigValue@3c0c8e50}comment=null}, value=ninja.leaping.configurate.MapConfigValue@6ec6d243}comment=null}, value=ninja.leaping.configurate.ScalarConfigValue@4013001f}comment=null}

I will give this a look at, codeHusky, thank you…

ADDED:
Okay, thank you codeHusky, that looks a lot like where most of my code was hovering earlier, with the original intent being to get a collection of nodes and then iterate through them, look up the node value, stuff the map.
I don’t know how yours actually works, because getNode() returns a configurationnode something object , but that was close enough, I can get the value of the node.

For the purposes of future searchers, here is the loading code:

        this.config = getConfigManager().load();
        Map<Object, ? extends ConfigurationNode> mapResult = config.getNode("sample").getChildrenMap();
        for (Object prekey : mapResult.keySet()) {
            String key = (String) prekey;
         //   System.out.println(key + "::" + config.getNode("sample", key).getDouble());
            this.alphasettings.put(key, config.getNode("sample", key).getDouble());
        }

Ideally you’d be able to go mapResult.get(preKey).getDouble()

@TheBoomer @codeHusky

Woah, that isn’t how you should be doing that at all! To understand how getChildrenMap() works, let’s use the example in the OP.

sample {
    alpha=4.25
    beta=3.75
    gamma=3.75
    delta=5.75
    epsilon=4.25    
}

There are six different nodes in this section - the sample node, which contains nodes alpha, beta, gamma, delta, and epsilon.

First, understand that each and every node has two things - a key, and a value. In the case of our Greek quintet, the keys are simple the letters and the values are all of type Double.

Though our sample node is like a list of nodes, it doesn’t make sense to store them like that because they’d be no easy way to retrieve them! Instead, they’re stored as a map of keys to values so we can retrieve them by only knowing the key.

In fact, getList(TypeToken.of(ConfigurationNode.class)) is equivalent to getChildrenMap().values(). However, always use getChildrenMap()!

Now that that’s out of the way, I think the main confusion you have comes from Java based things with iterating through Maps. There’s no sense in iterating through the keys to simply get the value back later, which can be a huge performance throttle for large maps.

Instead, use Map.Entrys any time you need both the key and the value of a map. Likewise, if you only need the keys use keySet(), and for the values use values() - use only what you need, and don’t use one to get the other!

In conclusion, we’re left with this much more readable (and performant!) for loop.

for (Map.Entry<Object, ? extends ConfigurationNode> entry : config.getNode("sample").getChildrenMap()) {
    this.alphasettings.put((String) entry.getKey(), entry.getValue().getDouble());
}
1 Like

Barely. Since this is a config loading mechanism, it shouldn’t be running constantly. If you want more straight forward code, go ahead and use mine. If you want minified code, use Simons method. Either way, it really doesn’t matter in the long run since they both work.

I personally don’t like stuff like entries and such since those turn easy to read, simple code into more of an inconsistent mess. If you do what simons doing, you should almost do that everywhere and not just in your config mechanism.

Also, in all honesty, if your config is so big that it is literally slowing down the server to a crash when it’s loading, you need to consider async loading and use of a database driver.

We’ll agree to disagree about what’s straightforward or not. I think we can all agree that there are some practices that are more accepted than others, and even more that should be avoided completely. Personally, seeing as Map.Entry was specifically added to iterate through a map, I think that’s the way it should be done - seems a bit clearer to me as well.

And yes, the point of explaining Map.Entry and providing a link to the javadocs was not so it could simply be used in a config file - it should be used anywhere you’re iterating through a Map and need both the key and the value.

1 Like

The main thing with that is that it really won’t boost your performance in an actual realistic situation by that much, but otherwise yeah, there’s obvious benefits.

So says the theory perhaps, but something technical is missing, because when I put that code in my IDE, it shows error on config.getNode(“sample”).getChildrenMap()) as can only iterate over array or java iterable… Which is where all the getting stuck was happening originally, unable to iterate over the map…

Functional trumps readable yet non compilable. Unless you can figure how to fix that.

should be

config.getNode(“sample”).getChildrenMap().entrySet()

if you want that to way work

3 Likes

Whoops! Thanks for the catch!