Skip to content

Commit

Permalink
feat(gson): add support for json array
Browse files Browse the repository at this point in the history
  • Loading branch information
Siroshun09 committed May 8, 2024
1 parent 842c764 commit 861b8a7
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 82 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2024 Siroshun09
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.github.siroshun09.configapi.format.gson;

import com.github.siroshun09.configapi.core.file.FileFormat;
import com.github.siroshun09.configapi.core.node.Node;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import com.google.gson.TypeAdapter;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

abstract class AbstractGsonFormat<N extends Node<?>> implements FileFormat<N> {

private final Gson gson;
private final Class<N> nodeClass;

protected AbstractGsonFormat(@NotNull GsonBuilder builder, @NotNull Class<N> nodeClass, @NotNull TypeAdapter<N> adapter) {
this.gson = builder.registerTypeAdapter(nodeClass, adapter).create();
this.nodeClass = nodeClass;
}

@Override
public @NotNull N load(@NotNull Reader reader) throws IOException {
try {
var node = this.gson.fromJson(reader, this.nodeClass);
return node != null ? node : this.createEmptyNode();
} catch (JsonIOException e) {
throw new IOException(e);
}
}

@Override
public void save(@NotNull N node, @NotNull Writer writer) throws IOException {
try {
this.gson.toJson(node, this.nodeClass, writer);
} catch (JsonIOException e) {
throw new IOException(e);
}
}

protected abstract @NotNull N createEmptyNode();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2024 Siroshun09
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.github.siroshun09.configapi.format.gson;

import com.github.siroshun09.configapi.core.file.FileFormat;
import com.github.siroshun09.configapi.core.node.CommentedNode;
import com.github.siroshun09.configapi.core.node.EnumValue;
import com.github.siroshun09.configapi.core.node.ListNode;
import com.github.siroshun09.configapi.core.node.MapNode;
import com.github.siroshun09.configapi.core.node.Node;
import com.github.siroshun09.configapi.core.node.NullNode;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jetbrains.annotations.NotNull;

/**
* A {@link FileFormat} implementation that loading/saving {@link ListNode} from/to json files using {@link Gson}.
* <p>
* Supported {@link Node}s:
*
* <ul>
* <li>{@link com.github.siroshun09.configapi.core.node.ValueNode}s
* <ul>
* <li>{@link com.github.siroshun09.configapi.core.node.CharValue} and {@link com.github.siroshun09.configapi.core.node.CharArray} is written as {@link String}</li>
* <li>{@link EnumValue} will be written as {@link String} using {@link Enum#name()}</li>
* <li>Loading {@link com.github.siroshun09.configapi.core.node.CharValue}, {@link com.github.siroshun09.configapi.core.node.CharArray}, and {@link EnumValue} is not supported</li>
* </ul>
* </li>
* <li>{@link MapNode} and {@link ListNode}</li>
* <li>{@link com.github.siroshun09.configapi.core.node.ArrayNode}s: serialize only</li>
* <li>{@link NullNode}</li>
* <li>{@link CommentedNode} - The comment will be dropped</li>
* </ul>
*/
public final class GsonArrayFormat extends AbstractGsonFormat<ListNode> {

/**
* An instance of {@link GsonArrayFormat} that created from a plain {@link GsonBuilder}.
*/
public static final GsonArrayFormat DEFAULT = new GsonArrayFormat(new GsonBuilder());

/**
* An instance of {@link GsonArrayFormat} that created from a {@link GsonBuilder} that set pretty printing.
*/
public static final GsonArrayFormat PRETTY_PRINTING = new GsonArrayFormat(new GsonBuilder().setPrettyPrinting());

/**
* Creates a new {@link GsonFormat} from the {@link GsonBuilder}.
*
* @param gsonBuilder the {@link GsonBuilder}
*/
public GsonArrayFormat(@NotNull GsonBuilder gsonBuilder) {
super(gsonBuilder, ListNode.class, NodeAdapter.LIST_NODE_ADAPTER);
}

@Override
protected @NotNull ListNode createEmptyNode() {
return ListNode.create();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,8 @@
import com.github.siroshun09.configapi.core.node.NullNode;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

/**
* A {@link FileFormat} implementation that loading/saving {@link MapNode} from/to json files using {@link Gson}.
* <p>
Expand All @@ -51,7 +46,7 @@
* <li>{@link CommentedNode} - The comment will be dropped</li>
* </ul>
*/
public final class GsonFormat implements FileFormat<MapNode> {
public final class GsonFormat extends AbstractGsonFormat<MapNode> {

/**
* An instance of {@link GsonFormat} that created from a plain {@link GsonBuilder}.
Expand All @@ -63,33 +58,17 @@ public final class GsonFormat implements FileFormat<MapNode> {
*/
public static final GsonFormat PRETTY_PRINTING = new GsonFormat(new GsonBuilder().setPrettyPrinting());

private final Gson gson;

/**
* Creates a new {@link GsonFormat} from the {@link GsonBuilder}.
*
* @param gsonBuilder the {@link GsonBuilder}
*/
public GsonFormat(@NotNull GsonBuilder gsonBuilder) {
this.gson = gsonBuilder.registerTypeAdapter(MapNode.class, NodeSerializer.INSTANCE).create();
}

@Override
public @NotNull MapNode load(@NotNull Reader reader) throws IOException {
try {
var node = this.gson.fromJson(reader, MapNode.class);
return node != null ? node : MapNode.create();
} catch (JsonIOException e) {
throw new IOException(e);
}
super(gsonBuilder, MapNode.class, NodeAdapter.MAP_NODE_ADAPTER);
}

@Override
public void save(@NotNull MapNode node, @NotNull Writer writer) throws IOException {
try {
this.gson.toJson(node, MapNode.class, writer);
} catch (JsonIOException e) {
throw new IOException(e);
}
protected @NotNull MapNode createEmptyNode() {
return MapNode.create();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,35 +48,85 @@

import java.io.IOException;

final class NodeSerializer extends TypeAdapter<MapNode> {
final class NodeAdapter extends TypeAdapter<Node<?>> {

static final NodeSerializer INSTANCE = new NodeSerializer();
static final TypeAdapter<Node<?>> INSTANCE = new NodeAdapter();

private NodeSerializer() {
}
static final TypeAdapter<ListNode> LIST_NODE_ADAPTER = new TypeAdapter<>() {
@Override
public ListNode read(JsonReader in) throws IOException {
var listNode = ListNode.create();

@Override
public MapNode read(JsonReader in) throws IOException {
var mapNode = MapNode.create();
var token = in.peek();

var token = in.peek();
if (token == JsonToken.BEGIN_ARRAY) {
in.beginArray();

if (token == JsonToken.BEGIN_OBJECT) {
in.beginObject();
while (in.hasNext()) {
listNode.add(NodeAdapter.INSTANCE.read(in));
}

in.endArray();

while (in.hasNext()) {
mapNode.set(in.nextName(), this.readNode(in));
return listNode;
} else {
throw new IOException("Unexpected token: " + token);
}
}

in.endObject();
} else {
throw new IOException("Unexpected token: " + token);
@Override
public void write(JsonWriter out, ListNode value) throws IOException {
out.beginArray();

for (var element : value.value()) {
NodeAdapter.INSTANCE.write(out, element);
}

out.endArray();
}
};

static final TypeAdapter<MapNode> MAP_NODE_ADAPTER = new TypeAdapter<>() {

@Override
public MapNode read(JsonReader in) throws IOException {
var mapNode = MapNode.create();

var token = in.peek();

if (token == JsonToken.BEGIN_OBJECT) {
in.beginObject();

while (in.hasNext()) {
mapNode.set(in.nextName(), NodeAdapter.INSTANCE.read(in));
}

in.endObject();
} else {
throw new IOException("Unexpected token: " + token);
}

return mapNode;
}

@Override
public void write(JsonWriter out, MapNode value) throws IOException {
out.beginObject();

for (var entry : value.value().entrySet()) {
out.name(String.valueOf(entry.getKey()));
NodeAdapter.INSTANCE.write(out, entry.getValue());
}

out.endObject();
}
};

return mapNode;
private NodeAdapter() {
}

private Node<?> readNode(JsonReader in) throws IOException {
@Override
public Node<?> read(JsonReader in) throws IOException {
var token = in.peek();

if (token == JsonToken.STRING) {
Expand All @@ -88,26 +138,9 @@ private Node<?> readNode(JsonReader in) throws IOException {
} else if (token == JsonToken.NULL) {
return NullNode.NULL;
} else if (token == JsonToken.BEGIN_ARRAY) {
in.beginArray();
var listNode = ListNode.create();

while (in.hasNext()) {
listNode.add(this.readNode(in));
}

in.endArray();

return listNode;
return LIST_NODE_ADAPTER.read(in);
} else if (token == JsonToken.BEGIN_OBJECT) {
in.beginObject();
var mapNode = MapNode.create();

while (in.hasNext()) {
mapNode.set(in.nextName(), this.readNode(in));
}

in.endObject();
return mapNode;
return MAP_NODE_ADAPTER.read(in);
} else {
throw new IOException("Unexpected token: " + token);
}
Expand All @@ -127,11 +160,7 @@ private NumberValue readNumber(final JsonReader in) throws IOException {
}

@Override
public void write(JsonWriter out, MapNode value) throws IOException {
writeNode(out, value);
}

private void writeNode(JsonWriter out, Node<?> value) throws IOException {
public void write(JsonWriter out, Node<?> value) throws IOException {
if (value instanceof StringValue stringValue) {
out.value(stringValue.value());
} else if (value instanceof EnumValue<?> enumValue) {
Expand All @@ -153,22 +182,9 @@ private void writeNode(JsonWriter out, Node<?> value) throws IOException {
} else if (value instanceof NullNode || value == null) {
out.nullValue();
} else if (value instanceof ListNode listNode) {
out.beginArray();

for (var element : listNode.value()) {
writeNode(out, element);
}

out.endArray();
LIST_NODE_ADAPTER.write(out, listNode);
} else if (value instanceof MapNode mapNode) {
out.beginObject();

for (var entry : mapNode.value().entrySet()) {
out.name(String.valueOf(entry.getKey()));
writeNode(out, entry.getValue());
}

out.endObject();
MAP_NODE_ADAPTER.write(out, mapNode);
} else if (value instanceof ArrayNode<?>) {
out.beginArray();

Expand Down Expand Up @@ -208,7 +224,7 @@ private void writeNode(JsonWriter out, Node<?> value) throws IOException {

out.endArray();
} else if (value instanceof CommentedNode<?> commentedNode) {
writeNode(out, commentedNode.node());
write(out, commentedNode.node());
} else {
throw new IOException("Cannot serialize " + value.getClass().getName());
}
Expand Down

0 comments on commit 861b8a7

Please sign in to comment.