M. Rummens M. Rummens - 2 months ago 47
JSON Question

JavaFX TreeView JSON Ex/Import via GSON

I´m looking for a way to export a JavaFX TreeView to JSON. To make this whole process simple, I use GSON. Its exporting the value of a treeItem well, but when I try to use the whole Tree its ending in a stack overflow. I believe this has something to do with the parent/child attribute. Is there a way to prevent GSON from exporting this attribute.

And how do I import the whole thing again? I wasn't able to import a simple object of mine, because GSON can't handle Properties.

Answer

You need to use a custom type adapter. Furthermore you can prevent stackoverflows by using loops instead of recursion:

public class TreeItemTypeAdapter<T> extends TypeAdapter<TreeItem<T>> {

    private Gson gson;

    public void setGson(Gson gson) {
        this.gson = gson;
    }

    private final Class<T> valueClass;

    public TreeItemTypeAdapter(Class<T> valueClass) {
        if (valueClass == null) {
            throw new IllegalArgumentException();
        }
        this.valueClass = valueClass;
    }

    public static TreeItemTypeAdapter<String> createStringTreeItemAdapter() {
        return new TreeItemTypeAdapter<>(String.class);
    }

    private void writeValue(JsonWriter writer, T t) throws IOException {
        if (gson == null) {
            writer.value(Objects.toString(t, null));
        } else {
            gson.toJson(t, valueClass, writer);
        }
    }

    private T readValue(JsonReader reader) throws IOException {
        if (gson == null) {
            Object value = reader.nextString();
            return (T) value;
        } else {
            return gson.fromJson(reader, valueClass);
        }
    }

    @Override
    public void write(JsonWriter writer, TreeItem<T> t) throws IOException {
        writer.beginObject().name("value");
        writeValue(writer, t.getValue());
        writer.name("children").beginArray();
        LinkedList<Iterator<TreeItem<T>>> iterators = new LinkedList<>();
        iterators.add(t.getChildren().iterator());
        while (!iterators.isEmpty()) {
            Iterator<TreeItem<T>> last = iterators.peekLast();
            if (last.hasNext()) {
                TreeItem<T> ti = last.next();
                writer.beginObject().name("value");
                writeValue(writer, ti.getValue());
                writer.name("children").beginArray();
                iterators.add(ti.getChildren().iterator());
            } else {
                writer.endArray().endObject();
                iterators.pollLast();
            }
        }
    }

    @Override
    public TreeItem<T> read(JsonReader reader) throws IOException {
        if (gson == null && !valueClass.getName().equals("java.lang.String")) {
            throw new IllegalStateException("cannot parse classes other than String without gson provided");
        }
        reader.beginObject();
        if (!"value".equals(reader.nextName())) {
            throw new IOException("value expected");
        }
        TreeItem<T> root = new TreeItem<>(readValue(reader));
        TreeItem<T> item = root;
        if (!"children".equals(reader.nextName())) {
            throw new IOException("children expected");
        }
        reader.beginArray();
        int depth = 1;
        while (depth > 0) {
            if (reader.hasNext()) {
                reader.beginObject();
                if (!"value".equals(reader.nextName())) {
                    throw new IOException("value expected");
                }
                TreeItem<T> newItem = new TreeItem<>(readValue(reader));
                item.getChildren().add(newItem);
                item = newItem;
                if (!"children".equals(reader.nextName())) {
                    throw new IOException("children expected");
                }
                reader.beginArray();
                depth++;
            } else {
                depth--;
                reader.endArray();
                reader.endObject();
                item = item.getParent();
            }

        }
        return root;
    }

}
public static void main(String[] args) {
    TreeItem<String> ti = new TreeItem<>("Hello world");
    TreeItem<String> ti2 = new TreeItem<>("42");
    TreeItem<String> ti3 = new TreeItem<>("Foo");
    TreeItem<String> ti4 = new TreeItem<>("Bar");
    ti.getChildren().addAll(ti2, ti3);
    ti2.getChildren().add(ti4);

    TreeItemTypeAdapter<String> adapter = new TreeItemTypeAdapter<>(String.class);
    Gson gson = new GsonBuilder().registerTypeAdapter(TreeItem.class, adapter).create();
    adapter.setGson(gson);

    System.out.println(gson.toJson(ti));
    System.out.println(toString(gson.fromJson("{\"value\":\"Hello world\",\"children\":[{\"value\":\"42\",\"children\":[{\"value\":\"Bar\",\"children\":[]}]},{\"value\":\"Foo\",\"children\":[]}]}",
            TreeItem.class)));
}

private static String toString(TreeItem ti) {
    StringBuilder sb = new StringBuilder("TreeItem [ value: \"").append(ti.getValue()).append("\" children [");
    boolean notFirst = false;
    for (TreeItem i : (List<TreeItem>) ti.getChildren()) {

        if (notFirst) {
            sb.append(",");
        } else {
            notFirst = true;
        }
        sb.append(toString(i));
    }
    return sb.append("]]").toString();
}
Comments