Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Saving notes to JSON

So far notes live only in memory: close the window and everything is gone. Let's save them to a file in JSON format. We will not write JSON by hand; the Jackson library does that, turning a list of Note records into text and back. The course uses Jackson 3: its packages start with tools.jackson, and it understands Java records with no setup.

The dependency and the module

Add to pom.xml:

<!-- pom.xml — new dependency -->
<dependency>
    <groupId>tools.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>3.2.3</version>
</dependency>

And require the Jackson module in module-info.java:

// src/main/java/module-info.java
module com.example.notes {
    requires javafx.controls;
    requires javafx.fxml;
    requires tools.jackson.databind;
 
    opens com.example.notes to javafx.fxml;
    exports com.example.notes;
}

The Note record is public and lives in an exported package, so Jackson reads it through the ordinary title() and text() methods and the public constructor.

The storage class

Let's move file handling into a separate class; the controller has no need to know how JSON works:

// src/main/java/com/example/notes/NoteStorage.java
package com.example.notes;
 
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.json.JsonMapper;
 
public class NoteStorage {
 
    private final Path file;
    private final JsonMapper mapper = JsonMapper.builder()
            .enable(SerializationFeature.INDENT_OUTPUT)
            .build();
 
    public NoteStorage(Path file) {
        this.file = file;
    }
 
    public List<Note> load() {
        if (Files.notExists(file)) {
            return List.of();
        }
        return mapper.readValue(file.toFile(), new TypeReference<List<Note>>() {});
    }
 
    public void save(List<Note> notes) {
        mapper.writeValue(file.toFile(), notes);
    }
}

JsonMapper is Jackson's main object. INDENT_OUTPUT turns on indentation so the file is easy to read. TypeReference tells Jackson the type of the list elements: because of type erasure, List.class does not carry it. Read errors in Jackson 3 are unchecked exceptions, so no throws is needed.

Connecting it to the controller

The sample notes are no longer needed: the list starts empty and is filled from the file:

// src/main/java/com/example/notes/MainController.java — fields and the start of initialize()
private final ObservableList<Note> notes = FXCollections.observableArrayList();
private final NoteStorage storage = new NoteStorage(
        Path.of(System.getProperty("user.home"), "notes.json"));
 
// in initialize(), after table.setItems(notes):
notes.setAll(storage.load());
notes.addListener((ListChangeListener<Note>) change -> storage.save(notes));

The file lives in the user's home folder: C:\Users\Name\notes.json on Windows, /home/name/notes.json on Linux. setAll replaces the list content with what was loaded. The listener fires on every addition, removal and replacement, so a separate "Save" button is not needed. The cast to ListChangeListener tells the compiler which of the two addListener methods to call.

Trying it out

Run the application: the table is empty, "Notes: 0". Add "Buy milk" with the text "2 liters" and the "Important" check box ticked, then "Call Oleg". A notes.json file appears in your home folder:

[ {
  "title" : "Buy milk",
  "text" : "2 liters",
  "category" : "Ideas",
  "important" : true
}, {
  "title" : "Call Oleg",
  "text" : "",
  "category" : "Ideas",
  "important" : false
} ]

Close the window and start the application again: both notes are still there.

Common mistake

If the file is damaged, for example someone edited it by hand and left an extra comma, load() throws an exception and the window does not open: tools.jackson.core.exc.StreamReadException: Unexpected character ('}' (code 125)): was expecting double-quote to start property name. In the next lesson loading moves into a background task, and such an error becomes a message in the status line instead of a crash.

Thanks for reading!

Donate to the site — we work hard for you.