Skip to content

Commit

Permalink
[json-node] JsonNodeMapper add type support for customer JsonAdapters (
Browse files Browse the repository at this point in the history
…#311)

* [json-node] JsonNodeMapper add type support for customer JsonAdapters

For cases where we want to manually build a JsonAdapter and then use that via JsonNodeMapper.

For example, manually build JsonAdapter using JsonObject.extract() methods to extract out values and deal with number types etc.

* [json-node] JsonNodeMapper - fix javadoc
  • Loading branch information
rbygrave authored Dec 12, 2024
1 parent bcf44c4 commit b59392d
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 4 deletions.
9 changes: 9 additions & 0 deletions json-node/src/main/java/io/avaje/json/node/JsonNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ default String extract(String path, String missingValue) {
throw new UnsupportedOperationException();
}

/**
* Extract the int from the given path if present or the given default value.
*
* @param missingValue The value to use when the path is missing.
*/
default int extract(String path, int missingValue) {
throw new UnsupportedOperationException();
}

/**
* Extract the long from the given path if present or the given default value.
*
Expand Down
13 changes: 11 additions & 2 deletions json-node/src/main/java/io/avaje/json/node/JsonNodeMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ static Builder builder() {
return new NodeAdapterBuilder();
}

/**
* Create a NodeMapper for the specific type given the JsonAdapter.
*
* @param customAdapter The type specific JsonAdapter to use.
* @param <T> The specific custom type being mapped.
* @return The type specific NodeMapper.
*/
<T> NodeMapper<T> mapper(JsonAdapter<T> customAdapter);

/**
* Return a NodeMapper for ANY json content.
* <p>
Expand Down Expand Up @@ -80,8 +89,8 @@ static Builder builder() {
* var asJson = mapper.toJson(jsonArray);
* }</pre>
*
* @see NodeMapper#toJson(JsonNode, OutputStream)
* @see NodeMapper#toJson(JsonNode, Writer)
* @see NodeMapper#toJson(Object, OutputStream)
* @see NodeMapper#toJson(Object, Writer)
*/
String toJson(JsonNode node);

Expand Down
8 changes: 8 additions & 0 deletions json-node/src/main/java/io/avaje/json/node/JsonObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,14 @@ public String extract(String path, String missingValue) {
return name == null ? missingValue : name.text();
}

@Override
public int extract(String path, int missingValue) {
final var node = find(path);
return !(node instanceof JsonNumber)
? missingValue
: ((JsonNumber) node).intValue();
}

@Override
public long extract(String path, long missingValue) {
final var node = find(path);
Expand Down
2 changes: 1 addition & 1 deletion json-node/src/main/java/io/avaje/json/node/NodeMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* @see JsonNodeMapper#objectMapper()
* @see JsonNodeMapper#nodeMapper()
*/
public interface NodeMapper<T extends JsonNode> {
public interface NodeMapper<T> {

/**
* Read the return the value from the json content.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ final class DJsonNodeMapper implements JsonNodeMapper {
this.arrayAdapter = arrayAdapter;
}

@Override
public <T> NodeMapper<T> mapper(JsonAdapter<T> customAdapter) {
return new DMapper<>(customAdapter, jsonStream);
}

@Override
public NodeMapper<JsonNode> nodeMapper() {
return new DMapper<>(nodeAdapter, jsonStream);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import java.io.*;

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

private final JsonAdapter<T> adapter;
private final JsonStream jsonStream;
Expand Down
63 changes: 63 additions & 0 deletions json-node/src/test/java/io/avaje/json/node/CustomAdapterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.avaje.json.node;

import io.avaje.json.JsonAdapter;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class CustomAdapterTest {

static final JsonNodeMapper mapper = JsonNodeMapper.builder().build();

@Test
void mapUsingCustomAdapter() {

MyAdapter myAdapter = new MyAdapter(mapper);

NodeMapper<MyCustomType> typeMapper = mapper.mapper(myAdapter);

MyCustomType source = new MyCustomType();
source.foo = "hi";
source.bar = 42;
String asJson = typeMapper.toJson(source);

MyCustomType fromJson = typeMapper.fromJson(asJson);

assertThat(fromJson.foo).isEqualTo(source.foo);
assertThat(fromJson.bar).isEqualTo(source.bar);
}

static class MyCustomType {
public String foo;
public int bar;
}

static class MyAdapter implements JsonAdapter<MyCustomType> {

final NodeMapper<JsonObject> objectMapper;

public MyAdapter(JsonNodeMapper mapper) {
this.objectMapper = mapper.objectMapper();
}

@Override
public void toJson(JsonWriter writer, MyCustomType value) {
var jsonObject = JsonObject.create()
.add("foo", value.foo)
.add("bar", value.bar);
objectMapper.toJson(jsonObject, writer);
}

@Override
public MyCustomType fromJson(JsonReader reader) {
JsonObject jsonObject = objectMapper.fromJson(reader);

MyCustomType myCustomType = new MyCustomType();
myCustomType.foo = jsonObject.extract("foo");
myCustomType.bar = jsonObject.extract("bar", 0);
return myCustomType;
}
}
}

0 comments on commit b59392d

Please sign in to comment.