Search code examples
javayamleditbungeecord

Edit yaml in a precise way


I would like to edit a yaml in java , ( I'm editing BungeeCord config file to create a system that launches instance of bungeecord with a defined by user port ) but in a precise way , i need to write exactly this in the yaml file :

listeners:
- query_port: 25577
  motd: '&1Another Bungee server'
  tab_list: GLOBAL_PING
  query_enabled: false
  proxy_protocol: false
  ping_passthrough: false
  priorities:
  - lobby
  bind_local_address: true
  host: 0.0.0.0:25577
  max_players: 1
  tab_size: 60
  force_default_server: false

I did something very similar but there is a vertical bar that prevents BungeeCord from reading the file :

public class ProxyYaml {
    HashMap<String, Object> entries = new HashMap<String, Object>();
    
    public ProxyYaml() {
        entries.put("query_port", 25577);
        entries.put("motd", "Hey Guys");
        entries.put("tab_list", "GLOBAL_PING");
        entries.put("query_enabled", false);
        entries.put("proxy_protocol", false);
        entries.put("ping_passthrough", false);
        entries.put("priorities", Arrays.asList("lobby"));
        entries.put("bind_local_address", true);
        entries.put("host", "0.0.0.0:25577");
        entries.put("max_players", 1);
        entries.put("tab_size", 60);
        entries.put("force_default_server", false);
    }
    
    public ArrayList<String> getProperties() {
        ArrayList<String> finalString = new ArrayList<String>();
        for(String entry : entries.keySet()) {
            finalString.add(entry + ": " + entries.get(entry).toString());
        }
        return finalString;
    }
}

( I'm using SimpleYaml api but I can change the api if needed )

                    File propsProxyFile = new File(path + "config.yml");
                    YamlFile propsProxyYaml = new YamlFile(propsProxyFile);
                    try {
                        propsProxyYaml.load(propsProxyFile);
                        propsProxyYaml.set("listeners", Arrays.asList(new ProxyYaml().getProperties()));
                        propsProxyYaml.save(propsProxyFile);
                    } catch (IOException | InvalidConfigurationException e) {
                        System.out.println(MainNetwork.logo + "Can't load proxy properties file");
                        return;
                    }

There is the code output ( with the vertical bar ) :

listeners:
- |
  query_port: 25577
  motd: '&1Another Bungee server'
  tab_list: GLOBAL_PING
  query_enabled: false
  proxy_protocol: false
  ping_passthrough: false
  priorities:
  - lobby
  bind_local_address: true
  host: 0.0.0.0:25577
  max_players: 1
  tab_size: 60
  force_default_server: false

What should I do please ?


Solution

  • The pipe character (|) starts a YAML block scalar. It means that all the following lines are a literal string and not subject to further YAML parsing.

    There are lots of strange things happening in your code, let's go over them:


        public ArrayList<String> getProperties() {
            ArrayList<String> finalString = new ArrayList<String>();
            for(String entry : entries.keySet()) {
                finalString.add(entry + ": " + entries.get(entry).toString());
            }
            return finalString;
        }
    

    You are manually transforming a mapping into a list of strings here. Why do you do that? You expect the final YAML file to contain the key-value pairs as mapping, so you should not transform them into a list of strings.

    Let's discuss what happens here with a quick example: Assume we have this java Map:

    Map<String, String> value = Map.of("foo", "bar");
    

    If we directly serialize this to YAML, we would get

    foo: bar
    

    but if we pipe it through your method, we'll get

    - "foo: bar"
    

    i.e. a sequence of one scalar value – because we manually transformed the mapping entry into a string! This is not what you want.


        propsProxyYaml.set("listeners", Arrays.asList(new ProxyYaml().getProperties()));
    

    You call Arrays.asList on the return value of getProperties() which is of type ArrayList<String> so asList will return a value of type List<ArrayList<String>>, which has a single entry that is the list you built. It is unclear why you call Arrays.asList unless you want to have a list of lists. According to the desired YAML output, this is not what you want.


    Now let's discuss what the set method does. I don't really know SimpleYAML and frankly, its documentation is horrible as it basically only consists of the autogenerated API docs. The first parameter of the method is named path, which implies that it is not a simple mapping key.

    What apparently happens is that the List<ArrayList<String>> value is transformed into a YAML sequence with one scalar value, and that scalar value contains all the string values you produced, separated by newlines. Without proper documentation, it is impossible to say whether this is expected behavior or a bug. In any case, it makes no sense.


    Now the actual YAML contains a mapping at its root with one entry, whose key is the scalar listeners. Its value is a list that contains another mapping. This means, the type you actually want to serialize is

    Map<String, List<Map<String, Object>>>
    

    I suggest that you simply build a value of this type and use the SnakeYAML API, which does have proper documentation on how its serialization system works. SimpleYAML uses SnakeYAML under the hood anyway, and there seems to be no reason to use a poorly documented API with surprising behavior instead of a well-documented one.

    You can also create a custom Java class instead of using Maps. Then the keys would become class fields.