Skip to content

Modifying And Saving

GroundbreakingMC edited this page Oct 3, 2025 · 1 revision

Modifying And Saving

Learn how to modify TOML documents and save them back to files.

Loading a Document

First, load an existing TOML file:

Path configPath = Path.of("config.toml");
TomlDocument doc = Tomly.parse(configPath, false, PreserveOptions.defaultOptions());

Modifying Values

Using set()

The set() method adds or updates values:

// Simple string value
doc.set("app.name", new StringNode("NewApp", List.of(), null));

// Number value
doc.set("server.port", new NumberNode(9090L, List.of(), null));

// Boolean value
doc.set("app.debug", new BooleanNode(true, List.of(), null));

Node Constructors

Each Node type has a constructor:

// StringNode(value, headerComments, inlineComment)
StringNode stringNode = new StringNode("value", List.of(), null);

// NumberNode(value, headerComments, inlineComment)
NumberNode numberNode = new NumberNode(42L, List.of(), null);

// BooleanNode(value, headerComments, inlineComment)
BooleanNode boolNode = new BooleanNode(true, List.of(), null);

// ArrayNode(value, headerComments, inlineComment)
List<Node> nodeArray = List.of(new StringNode("value", List.of(), null));
ArrayNode boolNode = new ArrayNode(nodeArray, List.of(), null);

// DatetimeNode(value, headerComments, inlineComment)
DatetimeNode dateNode = new DatetimeNode(
    LocalDate.now(), List.of(), null
);

Line and column are used for error reporting and can be set to any value (commonly 1, 0).

Adding Comments

Include comments when creating nodes:

StringNode withComments = new StringNode(
    "MyApp",
    List.of("# Application name", "# Required field"),  // Header comments
    "Do not change"  // Inline comment
);

doc.set("name", withComments);

Nested Values

Use dot notation to set nested values:

// Creates or updates server.host
doc.set("server.host", new StringNode("192.168.1.10", List.of(), null));

// Creates or updates database.connection.timeout
doc.set("database.connection.timeout", new NumberNode(30L, 1, 0, List.of(), null));

Working with Collections

Creating Arrays

import com.github.groundbreakingmc.tomly.nodes.impl.ArrayNode;

// Create array of strings
List<Node> serverNodes = List.of(
    new StringNode("server1", List.of(), null),
    new StringNode("server2", List.of(), null),
    new StringNode("server3", List.of(), null)
);

ArrayNode serversArray = new ArrayNode(serverNodes, List.of(), null);
doc.set("servers", serversArray);

Output:

servers = ["server1", "server2", "server3"]

Creating Tables

import com.github.groundbreakingmc.tomly.nodes.impl.TableNode;

// Create a new table
Map<String, Node> dbConfig = new HashMap<>();
dbConfig.put("host", new StringNode("localhost", List.of(), null));
dbConfig.put("port", new NumberNode(5432L, List.of(), null));
dbConfig.put("username", new StringNode("admin", List.of(), null));

TableNode dbTable = new TableNode("database", dbConfig, List.of(), null);
doc.set("database", dbTable);

Output:

[database]
host = "localhost"
port = 5432
username = "admin"

Creating Array of Tables

// Create multiple server configs
List<Node> serverTables = new ArrayList<>();

for (int i = 1; i <= 3; i++) {
    Map<String, Node> server = new HashMap<>();
    server.put("name", new StringNode("web" + i, List.of(), null));
    server.put("ip", new StringNode("192.168.1." + (10 + i), List.of(), null));
    server.put("port", new NumberNode(8080L, List.of(), null));

    serverTables.add(new TableNode(server, List.of(), null));
}

ArrayNode serversArray = new ArrayNode(serverTables, List.of(), null);
doc.set("servers", serversArray);

Output:

[[servers]]
name = "web1"
ip = "192.168.1.11"
port = 8080

[[servers]]
name = "web2"
ip = "192.168.1.12"
port = 8080

[[servers]]
name = "web3"
ip = "192.168.1.13"
port = 8080

Saving Documents

Basic Save

Save to a file with default options:

WriterOptions options = WriterOptions.defaultOptions();
doc.save(Path.of("config.toml"), options);

Save with Comments

Preserve comments when saving:

WriterOptions options = WriterOptions.builder()
    .writeHeaderComments(true)
    .writeInlineComments(true)
    .writeBlankLines(true)
    .build();

doc.save(Path.of("config.toml"), options);

Save with Custom Formatting

Control line length and formatting:

WriterOptions options = WriterOptions.builder()
    .maxLineLength(80)
    .writeHeaderComments(true)
    .build();

doc.save(Path.of("config.toml"), options);

Using TomlWriter Directly

For more control, use TomlWriter class:

Write to File

import com.github.groundbreakingmc.tomly.writer.TomlWriter;

Map<String, Node> nodes = doc.value();
WriterOptions options = WriterOptions.defaultOptions();

TomlWriter.write(nodes, Path.of("output.toml"), options);

Convert to String

Map<String, Node> nodes = doc.value();
WriterOptions options = WriterOptions.defaultOptions();

String tomlString = TomlWriter.toString(nodes, options);
System.out.println(tomlString);

Write to Custom Writer

import java.io.FileWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;

// To file with custom writer
try (FileWriter writer = new FileWriter("config.toml", StandardCharsets.UTF_8)) {
    TomlWriter.write(doc.value(), writer, WriterOptions.defaultOptions());
}

// To string
StringWriter stringWriter = new StringWriter();
TomlWriter.write(doc.value(), stringWriter, WriterOptions.defaultOptions());
String result = stringWriter.toString();

Round-Trip Serialization

Load, modify, and save while preserving structure:

// Load with preservation
PreserveOptions parseOpts = PreserveOptions.builder()
    .preserveHeaderComments(true)
    .preserveInlineComments(true)
    .preserveBlankLines(true)
    .build();

TomlDocument doc = Tomly.parse(
    Path.of("config.toml"),
    false,
    parseOpts
);

// Modify values
doc.set("version", new StringNode("2.0.0", List.of(), null));
doc.set("updated_at", new DatetimeNode(
    OffsetDateTime.now(), 1, 0, List.of(), null
));

// Save with preservation
WriterOptions writeOpts = WriterOptions.builder()
    .writeHeaderComments(true)
    .writeInlineComments(true)
    .writeBlankLines(true)
    .build();

doc.save(Path.of("config.toml"), writeOpts);

Common Scenarios

Update Configuration Version

public void updateVersion(Path configPath, String newVersion) {
    TomlDocument doc = Tomly.parse(
        configPath,
        false,
        PreserveOptions.defaultOptions()
    );

    doc.set("version", new StringNode(newVersion, List.of(), null));
    doc.save(configPath, WriterOptions.defaultOptions());
}

Add New Configuration Section

public void addDatabaseConfig(Path configPath) {
    TomlDocument doc = Tomly.parse(
        configPath,
        false,
        PreserveOptions.defaultOptions()
    );

    // Create database section
    Map<String, Node> dbConfig = new HashMap<>();
    dbConfig.put("host", new StringNode("localhost", List.of(), null));
    dbConfig.put("port", new NumberNode(5432L, List.of(), null));
    dbConfig.put("username", new StringNode("admin", List.of(), null));
    dbConfig.put("password", new StringNode("secret", List.of(), null));
    
    TableNode dbTable = new TableNode(
        dbConfig,
        List.of(" Database configuration"),
        null
    );
    
    doc.set("database", dbTable);
    
    WriterOptions options = WriterOptions.builder()
        .writeHeaderComments(true)
        .build();
    
    doc.save(configPath, options);
}

Batch Update Values

public void updateMultipleValues(Path configPath, Map<String, String> updates) {
    TomlDocument doc = Tomly.parse(
        configPath, 
        false, 
        PreserveOptions.defaultOptions()
    );
    
    for (Map.Entry<String, String> entry : updates.entrySet()) {
        doc.set(entry.getKey(), new StringNode(
            entry.getValue(), 
            List.of(), 
            null
        ));
    }
    
    doc.save(configPath, WriterOptions.defaultOptions());
}

// Usage
Map<String, String> updates = Map.of(
    "app.name", "NewAppName",
    "app.version", "2.0.0",
    "server.host", "192.168.1.100"
);

updateMultipleValues(Path.of("config.toml"), updates);

Generate Configuration from Template

public TomlDocument createConfigFromDefaults() {
    Map<String, Node> root = new HashMap<>();
    
    // App section
    Map<String, Node> app = new HashMap<>();
    app.put("name", new StringNode(
        "MyApp",
        List.of("# Application name"),
        "required"
    ));
    app.put("version", new StringNode(
        "1.0.0",
        List.of("# Semantic version"),
        null
    ));
    app.put("debug", new BooleanNode(
        false,
        List.of("# Enable debug mode"),
        null
    ));
    
    root.put("app", new TableNode(app, List.of(), null));
    
    // Server section
    Map<String, Node> server = new HashMap<>();
    server.put("host", new StringNode("localhost", List.of(), null));
    server.put("port", new NumberNode(8080L, List.of(), null));
    
    root.put("server", new TableNode(
        "server",
        server, 
        List.of("# Server configuration"),
        null
    ));
    
    return new DocumentNode(new TableNode("", root, List.of(), null));
}

// Usage
TomlDocument template = createConfigFromDefaults();

WriterOptions options = WriterOptions.builder()
    .writeHeaderComments(true)
    .writeInlineComments(true)
    .build();

template.save(Path.of("config.template.toml"), options);

What Gets Preserved

When doing round-trip serialization:

Preserved

  • All values and their types
  • Table structure and nesting
  • Array of tables
  • Comments (if preservation enabled)
  • Blank lines (if preservation enabled)

May Change

  • Number formatting (hex/octal/binary converted to decimal)
  • String quote style (normalized)
  • Inline table formatting

Example:

Input:

port = 0x1F90

[database]
server = '192.168.1.1'

[database.credentials]
username = "admin"
password = "secret123"

Output:

port = 8080

[database]
server = "192.168.1.1"
credentials = {username = "admin", password = "secret123"}

Building Documents Programmatically

Create entire documents without parsing:

public class ConfigBuilder {
    
    public static TomlDocument buildConfig(
        String appName,
        String version,
        String host,
        int port
    ) {
        Map<String, Node> root = new HashMap<>();
        
        // Add simple values
        root.put("name", new StringNode(appName, List.of(), null));
        root.put("version", new StringNode(version, List.of(), null));
        
        // Add server section
        Map<String, Node> server = new HashMap<>();
        server.put("host", new StringNode(host, List.of(), null));
        server.put("port", new NumberNode((long) port, List.of(), null));
        
        root.put("server", new TableNode(server, List.of(), null));
        
        return new DocumentNode(new TableNode("", root, List.of(), null));
    }
    
    public static void main(String[] args) {
        TomlDocument config = buildConfig("MyApp", "1.0.0", "localhost", 8080);
        config.save(
            Path.of("generated-config.toml"), 
            WriterOptions.defaultOptions()
        );
    }
}

Error Handling

Handle errors during save operations:

import com.github.groundbreakingmc.tomly.exceptions.TomlSaveException;

try {
    doc.save(Path.of("config.toml"), WriterOptions.defaultOptions());
} catch (TomlSaveException ex) {
    System.err.println("Failed to save configuration: " + ex.getMessage());
    ex.printStackTrace();
}

Common causes:

  • No write permission
  • Disk full
  • Invalid path
  • I/O errors

Best Practices

  1. Use preservation judiciously: Only enable comment preservation if you need it
  2. Validate before saving: Ensure your modifications are valid
  3. Backup before modifying: Keep original files safe
  4. Use try-with-resources: For custom Writers
  5. Handle exceptions: Always catch TomlSaveException
  6. Test round-trips: Verify parse → modify → save cycles
  7. Document your changes: Add comments to explain modifications

Next Steps