Skip to content

Commit

Permalink
[json-node] Add NodeMapper API for reading/writing options using all …
Browse files Browse the repository at this point in the history
…options like InputStream, OutputStream etc
  • Loading branch information
rbygrave committed Dec 12, 2024
1 parent 5be794e commit 7e309c8
Show file tree
Hide file tree
Showing 7 changed files with 387 additions and 12 deletions.
76 changes: 68 additions & 8 deletions json-node/src/main/java/io/avaje/json/node/JsonNodeMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import io.avaje.json.node.adapter.NodeAdapterBuilder;
import io.avaje.json.stream.JsonStream;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;

/**
Expand Down Expand Up @@ -37,38 +41,94 @@ static Builder builder() {
}

/**
* Helper method to write the node to JSON.
* Return a NodeMapper for ANY json content.
* <p>
* The NodeMapper provides support for all reading and writing options
* such as InputStream, OutputStream, Reader, Writer etc.
*/
NodeMapper<JsonNode> nodeMapper();

/**
* Return a NodeMapper for json OBJECT content.
* <p>
* The NodeMapper provides support for all reading and writing options
* such as InputStream, OutputStream, Reader, Writer etc.
*/
NodeMapper<JsonObject> objectMapper();

/**
* Return a NodeMapper for json ARRAY content.
* <p>
* The NodeMapper provides support for all reading and writing options
* such as InputStream, OutputStream, Reader, Writer etc.
*/
NodeMapper<JsonArray> arrayMapper();

/**
* Write the node to JSON string.
* <p>
* For options to write json content to OutputStream, Writer etc
* use {@link #nodeMapper()}.
*
* <pre>{@code
* static final JsonNodeMapper node = JsonNodeMapper.builder().build();
* static final JsonNodeMapper mapper = JsonNodeMapper.builder().build();
*
* JsonArray jsonArray = JsonArray.create()
* .add(JsonInteger.of(42))
* .add(JsonString.of("foo"));
*
* var asJson = node.toJson(jsonArray);
* var asJson = mapper.toJson(jsonArray);
* }</pre>
*
* @see NodeMapper#toJson(JsonNode, OutputStream)
* @see NodeMapper#toJson(JsonNode, Writer)
*/
String toJson(JsonNode node);

/**
* Helper method to read JSON returning a JsonNode.
* Read any json content returning a JsonNode.
* <p>
* For options to read json content from InputStream, Reader etc
* use the fromJson methods on {@link NodeMapper}.
*
* <pre>{@code
* static final JsonNodeMapper node = JsonNodeMapper.builder().build();
* static final JsonNodeMapper mapper = JsonNodeMapper.builder().build();
*
* JsonNode nodeFromJson = node.fromJson(jsonContent);
* JsonNode nodeFromJson = mapper.fromJson(jsonContent);
* }</pre>
*
* @see NodeMapper#fromJson(Reader)
* @see NodeMapper#fromJson(InputStream)
*/
JsonNode fromJson(String json);

/**
* Read a JsonObject from json string content.
* <p>
* Use this when we know that the json content is a JsonObject.
*
* @param json The json content.
* @return The JsonObject parsed from the content.
*/
JsonObject fromJsonObject(String json);

/**
* Read a JsonArray from json string content.
* <p>
* Use this when we know that the json content is a JsonArray.
*
* @param json The json content.
* @return The JsonArray parsed from the content.
*/
JsonArray fromJsonArray(String json);

/**
* Helper method to read JSON with an expected JsonNode type.
*
* <pre>{@code
* static final JsonNodeMapper node = JsonNodeMapper.builder().build();
* static final JsonNodeMapper mapper = JsonNodeMapper.builder().build();
*
* JsonArray arrayFromJson = node.fromJson(JsonArray.class, jsonContent);
* JsonArray arrayFromJson = mapper.fromJson(JsonArray.class, jsonContent);
* }</pre>
*/
<T extends JsonNode> T fromJson(Class<T> type, String json);
Expand Down
78 changes: 78 additions & 0 deletions json-node/src/main/java/io/avaje/json/node/NodeMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package io.avaje.json.node;

import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;

/**
* Mappers for JsonNode, JsonArray and JsonObject.
* <p>
* This supports more options for reading and writing json content
* such as InputStream, OutputStream, Reader, Writer etc.
*
* @see JsonNodeMapper#arrayMapper()
* @see JsonNodeMapper#objectMapper()
* @see JsonNodeMapper#nodeMapper()
*/
public interface NodeMapper<T extends JsonNode> {

/**
* Read the return the value from the json content.
*/
T fromJson(String content);

/**
* Read the return the value from the reader.
*/
T fromJson(JsonReader reader);

/**
* Read the return the value from the json content.
*/
T fromJson(byte[] content);

/**
* Read the return the value from the reader.
*/
T fromJson(Reader reader);

/**
* Read the return the value from the inputStream.
*/
T fromJson(InputStream inputStream);

/**
* Return as json string.
*/
String toJson(T value);

/**
* Return as json string in pretty format.
*/
String toJsonPretty(T value);

/**
* Return the value as json content in bytes form.
*/
byte[] toJsonBytes(T value);

/**
* Write to the given writer.
*/
void toJson(T value, JsonWriter writer);

/**
* Write to the given writer.
*/
void toJson(T value, Writer writer);

/**
* Write to the given outputStream.
*/
void toJson(T value, OutputStream outputStream);

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@ final class DJsonNodeMapper implements JsonNodeMapper {
this.arrayAdapter = arrayAdapter;
}

@Override
public NodeMapper<JsonNode> nodeMapper() {
return new DMapper<>(nodeAdapter, jsonStream);
}

@Override
public NodeMapper<JsonObject> objectMapper() {
return new DMapper<>(objectAdapter, jsonStream);
}

@Override
public NodeMapper<JsonArray> arrayMapper() {
return new DMapper<>(arrayAdapter, jsonStream);
}

@Override
public String toJson(JsonNode node) {
final var writer = jsonStream.bufferedWriter();
Expand All @@ -43,6 +58,20 @@ public JsonNode fromJson(String json) {
}
}

@Override
public JsonObject fromJsonObject(String json) {
try (JsonReader reader = jsonStream.reader(json)) {
return objectAdapter.fromJson(reader);
}
}

@Override
public JsonArray fromJsonArray(String json) {
try (JsonReader reader = jsonStream.reader(json)) {
return arrayAdapter.fromJson(reader);
}
}

@Override
public <T extends JsonNode> T fromJson(Class<T> type, String json) {
JsonAdapter<T> adapter = adapter(type);
Expand Down
116 changes: 116 additions & 0 deletions json-node/src/main/java/io/avaje/json/node/adapter/DMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package io.avaje.json.node.adapter;

import io.avaje.json.JsonAdapter;
import io.avaje.json.JsonException;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.avaje.json.node.JsonNode;
import io.avaje.json.node.NodeMapper;
import io.avaje.json.stream.BufferedJsonWriter;
import io.avaje.json.stream.BytesJsonWriter;
import io.avaje.json.stream.JsonStream;

import java.io.*;

final class DMapper<T extends JsonNode> implements NodeMapper<T> {

private final JsonAdapter<T> adapter;
private final JsonStream jsonStream;

DMapper(JsonAdapter<T> adapter, JsonStream jsonStream) {
this.adapter = adapter;
this.jsonStream = jsonStream;
}

@Override
public T fromJson(JsonReader reader) {
return adapter.fromJson(reader);
}

@Override
public T fromJson(String content) {
try (JsonReader reader = jsonStream.reader(content)) {
return adapter.fromJson(reader);
}
}

@Override
public T fromJson(byte[] content) {
try (JsonReader reader = jsonStream.reader(content)) {
return adapter.fromJson(reader);
}
}

@Override
public T fromJson(Reader content) {
try (JsonReader reader = jsonStream.reader(content)) {
return adapter.fromJson(reader);
}
}

@Override
public T fromJson(InputStream content) {
try (JsonReader reader = jsonStream.reader(content)) {
return adapter.fromJson(reader);
}
}

@Override
public String toJson(T value) {
try (BufferedJsonWriter writer = jsonStream.bufferedWriter()) {
toJson(value, writer);
return writer.result();
}
}

@Override
public String toJsonPretty(T value) {
try (BufferedJsonWriter writer = jsonStream.bufferedWriter()) {
writer.pretty(true);
toJson(value, writer);
return writer.result();
}
}

@Override
public byte[] toJsonBytes(T value) {
try (BytesJsonWriter writer = jsonStream.bufferedWriterAsBytes()) {
toJson(value, writer);
return writer.result();
}
}

@Override
public void toJson(T value, JsonWriter writer) {
try {
adapter.toJson(writer, value);
} catch (RuntimeException e) {
writer.markIncomplete();
throw new JsonException(e);
}
}

@Override
public void toJson(T value, Writer writer) {
try (JsonWriter jsonWriter = jsonStream.writer(writer)) {
toJson(value, jsonWriter);
}
}

@Override
public void toJson(T value, OutputStream outputStream) {
try (JsonWriter writer = jsonStream.writer(outputStream)) {
toJson(value, writer);
}
close(outputStream);
}

private void close(Closeable outputStream) {
try {
outputStream.close();
} catch (IOException e) {
throw new UncheckedIOException("Error closing stream", e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class ExtractNodeTest {
@Test
void extract() {

JsonObject object = ExtractNodeTest.mapper.fromJson(JsonObject.class, content);
JsonObject object = mapper.fromJsonObject(content);
JsonArray arrayWithNestedPerson = (JsonArray) object.get("people");

List<JsonNode> peopleNodes =
Expand Down Expand Up @@ -79,7 +79,7 @@ void extract() {

@Test
void extractMissing_expect_IllegalArgumentException() {
JsonObject object = ExtractNodeTest.mapper.fromJson(JsonObject.class, content);
JsonObject object = ExtractNodeTest.mapper.fromJsonObject(content);

assertThatThrownBy(() -> object.extractNode("missing.path.here"))
.isInstanceOf(IllegalArgumentException.class)
Expand All @@ -88,7 +88,7 @@ void extractMissing_expect_IllegalArgumentException() {

@Test
void extractMissing_missing_object() {
JsonObject object = ExtractNodeTest.mapper.fromJson(JsonObject.class, content);
JsonObject object = ExtractNodeTest.mapper.fromJsonObject(content);

JsonNode result = object.extractNode("missing.path.here", JsonObject.empty());
assertThat(result).isSameAs(JsonObject.empty());
Expand Down
Loading

0 comments on commit 7e309c8

Please sign in to comment.