Carleslc / Simple-YAML

This Java API provides an easy-to-use way to store data and provide configurations using the YAML format.
https://carleslc.me/Simple-YAML
GNU General Public License v3.0
132 stars 38 forks source link

Key setting with single quote despite no special characters. #59

Closed ghost closed 2 years ago

ghost commented 2 years ago

I'm setting user's XP and level, using userYaml.set("users." + user.getId() + ".xp", 0); This generates users: '192230467519512576': xp: 0

Why is it adding single quotes to the user's ID, and how can I change this?

LOOHP commented 2 years ago

Because without quotes that would be a number not a string.

ghost commented 2 years ago

Because without quotes that would be a number not a string.

java.lang.String user.getId() the discord ID of the User

It returns a string, though? And even when doing String.valueOf(user.getID()); it still adds single quotes

LOOHP commented 2 years ago

I mean, in yaml, if strings with only numbers are not quoted, it'll be parsed as a number. So it needs to be quoted to remain as a string. Therefore the numbers themselves are the special characters.

ghost commented 2 years ago

I mean, in yaml, if strings with only numbers are not quoted, it'll be parsed as a number. So it needs to be quoted remain as a string.

After changing it around a little, it does seem you're right. My apologies, seems that is the case. But with that, I'm getting a new issue: getKeys is returning null when attempting to get all of the keys under users

portlek commented 2 years ago

if i'm not wrong, keys can't be number, only string.

ghost commented 2 years ago

if i'm not wrong, keys can't be number, only string.

I added characters to the key to make it a string users: 192230467519512576A: xp: 0

Code that I'm using to loop over the file

` ConfigurationSection users = userYaml.getConfigurationSection("users");

    Set<String> values = users.getKeys(false);

    for (String value : values) {
        System.out.println(users.getInt(value + ".xp"));
    }`
Carleslc commented 2 years ago

As stated, keys are yaml strings by default, so numbers are wrapped using single quotes in keys.

For values you can specify the quote style with set(String, Object, QuoteStyle) (#50) but not for keys. Maybe you could dump those keys with plain style using a custom snakeyaml representer using setImplementation, but probably is overcomplicating things for your use case.

For your second problem, what do you mean getKeys returns null? It cannot return null, at least it will be an empty set. The getConfigurationSection returns null if the path is not present in the file.

You can check for null to see if the section exists, or use the isConfigurationSection method and create a new section with createSection if it does not exist. If you are unsure if the users section exist or want to create it inside your code (instead of providing it as a resource) you could do something like this:

YamlFile userYaml = new YamlFile("users.yml");

// Create a new file if not exists, otherwise load its contents
userYaml.createOrLoad();

// Get users section if already created
ConfigurationSection users = userYaml.getConfigurationSection("users");

if (users == null) {
    // Create the section if it does not exist
    users = userYaml.createSection("users");
}

// Adding a new user
String newUserId = "192230467519512576";

users.set(newUserId + ".xp", 100);

// Reading users
Set<String> userIds = users.getKeys(false);

for (String userId : userIds) {
    int xp = users.getInt(userId + ".xp");
    System.out.println(userId + ": " + xp + " XP");
}

// Save changes
userYaml.save();

I've excluded exception handling.

Output

192230467519512576: 100 XP

users.yml

users:
  '192230467519512576':
    xp: 100

If your file only has users then you could avoid the users key entirely, but that's a personal preference.

I suggest you to have a look at the serialization example with the Person class to see an example of yaml serialization using java objects if you have created a User class to wrap the xp and other attributes, allowing you to directly save and retrieve the User objects to your yaml file.

Carleslc commented 2 years ago

As a reference for your first question, if needed, this is how you would set a custom representer to dump string integers without quotes in keys.

First, create the custom representer:

CustomKeyRepresenter.java

import org.simpleyaml.configuration.implementation.snakeyaml.SnakeYamlRepresenter;
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
import org.simpleyaml.configuration.implementation.snakeyaml.lib.nodes.*;
import org.simpleyaml.configuration.implementation.snakeyaml.lib.resolver.Resolver;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class CustomKeyRepresenter extends SnakeYamlRepresenter {

    private boolean isKey = false;

    public CustomKeyRepresenter(DumperOptions dumperOptions) {
        super(dumperOptions);
    }

    @Override
    protected Node representMapping(Tag tag, Map<?, ?> mapping, DumperOptions.FlowStyle flowStyle) {
        List<NodeTuple> value = new ArrayList<>(mapping.size());
        MappingNode node = new MappingNode(tag, value, flowStyle);
        this.representedObjects.put(this.objectToRepresent, node);

        for (Map.Entry<?, ?> entry : mapping.entrySet()) {
            this.isKey = true;
            Node nodeKey = this.representData(entry.getKey());
            this.isKey = false;
            Node nodeValue = this.representData(entry.getValue());
            value.add(new NodeTuple(nodeKey, nodeValue));
        }

        node.setFlowStyle(this.defaultFlowStyle);
        return node;
    }

    @Override
    protected Node representScalar(Tag tag, String value, DumperOptions.ScalarStyle style) {
        if (style == null) {
            style = this.defaultScalarStyle;
        }
        if (this.isKey && tag == Tag.STR && style == DumperOptions.ScalarStyle.PLAIN && isInt(value)) {
            tag = Tag.INT;
        }
        return new ScalarNode(tag, value, null, null, style);
    }

    private static boolean isInt(final String s) {
        return s != null && Resolver.INT.matcher(s).matches();
    }
}

Then, create the YamlFile with your custom representer:

// Create a YamlFile with a custom representer
SnakeYamlRepresenter customRepresenter = new CustomKeyRepresenter(new DumperOptions());
YamlFile yamlFile = new YamlFile(new SimpleYamlImplementation(customRepresenter));

// Set your file path
yamlFile.setConfigurationFile("users.yml");

Alternatively, you can also change the default implementation to use your custom representer after you have created the YamlFile:

YamlFile userYaml = new YamlFile("users.yml");

// Set custom key representer
DumperOptions dumperOptions = ((SnakeYamlImplementation) userYaml.getImplementation()).getDumperOptions();
SnakeYamlRepresenter customRepresenter = new CustomKeyRepresenter(dumperOptions);
userYaml.setImplementation(new SimpleYamlImplementation(customRepresenter));

// Same as above comment
// ...

users.yml

users:
  192230467519512576:
    xp: 100
ghost commented 2 years ago

As stated, keys are yaml strings by default, so numbers are wrapped using single quotes in keys.

For values you can specify the quote style with set(String, Object, QuoteStyle) (#50) but not for keys. Maybe you could dump those keys with plain style using a custom snakeyaml representer using setImplementation, but probably is overcomplicating things for your use case.

For your second problem, what do you mean getKeys returns null? It cannot return null, at least it will be an empty set. The getConfigurationSection returns null if the path is not present in the file.

You can check for null to see if the section exists, or use the isConfigurationSection method and create a new section with createSection if it does not exist. If you are unsure if the users section exist or want to create it inside your code (instead of providing it as a resource) you could do something like this:

YamlFile userYaml = new YamlFile("users.yml");

// Create a new file if not exists, otherwise load its contents
userYaml.createOrLoad();

// Get users section if already created
ConfigurationSection users = userYaml.getConfigurationSection("users");

if (users == null) {
    // Create the section if it does not exist
    users = userYaml.createSection("users");
}

// Adding a new user
String newUserId = "192230467519512576";

users.set(newUserId + ".xp", 100);

// Reading users
Set<String> userIds = users.getKeys(false);

for (String userId : userIds) {
    int xp = users.getInt(userId + ".xp");
    System.out.println(userId + ": " + xp + " XP");
}

// Save changes
userYaml.save();

I've excluded exception handling.

Output

192230467519512576: 100 XP

users.yml

users:
  '192230467519512576':
    xp: 100

If your file only has users then you could avoid the users key entirely, but that's a personal preference.

I suggest you to have a look at the serialization example with the Person class to see an example of yaml serialization using java objects if you have created a User class to wrap the xp and other attributes, allowing you to directly save and retrieve the User objects to your yaml file.

The getKeys was giving a nullPointerException but that's simply because I derped out and forgot to load the yaml file. So that's entirely my fault. Thank you for all the help!

Carleslc commented 1 year ago

I've updated the custom representer example comment above (https://github.com/Carleslc/Simple-YAML/issues/59#issuecomment-1100762648) to work for the latest version 1.8.4, as now the SnakeYamlRepresenter needs the DumperOptions to be passed in the constructor.