Skip to content

Commit deba5f9

Browse files
authored
[json-node] Fix NULL value bug with JsonObject (#322)
* [json-node] Fix NULL value bug with JsonObject Also fix DecimalAdapter * [json-node] Add JsonObject.extractOrEmpty() methods Returning Optional especially for String values mapping to other types.
1 parent ab3cc87 commit deba5f9

File tree

7 files changed

+70
-14
lines changed

7 files changed

+70
-14
lines changed

json-node/src/main/java/io/avaje/json/node/JsonNode.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.jspecify.annotations.Nullable;
44

55
import java.io.Serializable;
6+
import java.util.Optional;
67

78
/**
89
* Represents the core JSON types.
@@ -107,6 +108,13 @@ default String extract(String path) {
107108
throw new UnsupportedOperationException();
108109
}
109110

111+
/**
112+
* Extract the text from the given path if present else empty.
113+
*/
114+
default Optional<String> extractOrEmpty(String path) {
115+
throw new UnsupportedOperationException();
116+
}
117+
110118
/**
111119
* Extract the text from the given path if present or the given default value.
112120
*
@@ -172,4 +180,13 @@ default JsonNode extractNode(String path, JsonNode missingValue) {
172180
throw new UnsupportedOperationException();
173181
}
174182

183+
/**
184+
* Extract the node from the given path if present else empty.
185+
*
186+
* @return The node for the given path.
187+
*/
188+
default Optional<JsonNode> extractNodeOrEmpty(String path) {
189+
throw new UnsupportedOperationException();
190+
}
191+
175192
}

json-node/src/main/java/io/avaje/json/node/JsonObject.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22

33
import org.jspecify.annotations.Nullable;
44

5-
import java.util.Collections;
6-
import java.util.LinkedHashMap;
7-
import java.util.Map;
8-
import java.util.Objects;
5+
import java.util.*;
96
import java.util.regex.Pattern;
107

118
import static java.util.Objects.requireNonNull;
@@ -220,6 +217,12 @@ public String extract(String path) {
220217
return node.text();
221218
}
222219

220+
@Override
221+
public Optional<String> extractOrEmpty(String path) {
222+
final var name = find(path);
223+
return name == null ? Optional.empty() : Optional.of(name.text());
224+
}
225+
223226
@Override
224227
public String extract(String path, String missingValue) {
225228
final var name = find(path);
@@ -272,4 +275,10 @@ public JsonNode extractNode(String path, JsonNode missingValue) {
272275
final var node = find(path);
273276
return node != null ? node : missingValue;
274277
}
278+
279+
@Override
280+
public Optional<JsonNode> extractNodeOrEmpty(String path) {
281+
final var node = find(path);
282+
return Optional.ofNullable(node);
283+
}
275284
}

json-node/src/main/java/io/avaje/json/node/adapter/DecimalAdapter.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33
import io.avaje.json.JsonAdapter;
44
import io.avaje.json.JsonReader;
55
import io.avaje.json.JsonWriter;
6-
import io.avaje.json.node.JsonLong;
6+
import io.avaje.json.node.JsonDecimal;
77

8-
final class DecimalAdapter implements JsonAdapter<JsonLong> {
8+
final class DecimalAdapter implements JsonAdapter<JsonDecimal> {
99

1010
@Override
11-
public void toJson(JsonWriter writer, JsonLong value) {
12-
writer.value(value.longValue());
11+
public void toJson(JsonWriter writer, JsonDecimal value) {
12+
writer.value(value.decimalValue());
1313
}
1414

1515
@Override
16-
public JsonLong fromJson(JsonReader reader) {
17-
return JsonLong.of(reader.readLong());
16+
public JsonDecimal fromJson(JsonReader reader) {
17+
return JsonDecimal.of(reader.readDecimal());
1818
}
1919
}

json-node/src/main/java/io/avaje/json/node/adapter/NodeAdapter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ ObjectAdapter objectAdapter() {
3333
public JsonNode fromJson(JsonReader reader) {
3434
switch (reader.currentToken()) {
3535
case NULL:
36+
reader.isNullValue();
3637
return null;
3738
case BEGIN_ARRAY:
3839
return arrayAdapter.fromJson(reader);
@@ -55,8 +56,7 @@ public void toJson(JsonWriter writer, JsonNode value) {
5556
writer.nullValue();
5657
return;
5758
}
58-
JsonNode.Type type = value.type();
59-
switch (type) {
59+
switch (value.type()) {
6060
case NULL:
6161
writer.nullValue();
6262
break;

json-node/src/test/java/io/avaje/json/node/ExtractNodeTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ void extract() {
7575

7676
assertThat(missingOther).hasSize(2);
7777
assertThat(missingOther).containsExactly("BOther", "MISSING!");
78+
79+
List<String> missingOther2 =
80+
arrayWithNestedPerson.stream()
81+
.filter(node -> "family".equals(node.extract("type")))
82+
.flatMap(node -> node.extractNodeOrEmpty("person.other").stream())
83+
.map(JsonNode::text)
84+
.collect(Collectors.toList());
85+
86+
assertThat(missingOther2).hasSize(1);
87+
assertThat(missingOther2).containsExactly("BOther");
7888
}
7989

8090
@Test

json-node/src/test/java/io/avaje/json/node/JsonObjectTest.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class JsonObjectTest {
1919

2020
@Test
2121
void of() {
22-
Map<String,JsonNode> map = new LinkedHashMap<>();
22+
Map<String, JsonNode> map = new LinkedHashMap<>();
2323
map.put("name", JsonString.of("foo"));
2424
map.put("other", JsonInteger.of(42));
2525

@@ -141,6 +141,9 @@ void findNested() {
141141
assertThat(node.extract("person.active", false)).isEqualTo(true);
142142
assertThat(node.extract("person.missing", false)).isEqualTo(false);
143143

144+
assertThat(node.extractOrEmpty("person.missing")).isEmpty();
145+
assertThat(node.extractOrEmpty("person.name")).isNotEmpty().asString().contains("myName");
146+
144147
assertThat(node.extract("address.size", -1)).isEqualTo(42);
145148
assertThat(node.extract("address.junk", -1L)).isEqualTo(99L);
146149
assertThat(node.extract("address.notSize", -1)).isEqualTo(-1);
@@ -190,4 +193,13 @@ void toPlain() {
190193
assertThat(plainMap.get("name")).isEqualTo("foo");
191194
assertThat(plainMap.get("other")).isEqualTo(Map.of("b", 42));
192195
}
196+
197+
@Test
198+
void nullValuesInMap() {
199+
String s = "{\"a\":1,\"b\":null,\"c\":null,\"d\":4}";
200+
JsonNodeMapper mapper = JsonNodeMapper.builder().build();
201+
JsonObject jsonObject = mapper.fromJsonObject(s);
202+
203+
assertThat(jsonObject.elements().keySet()).containsExactly("a", "b", "c", "d");
204+
}
193205
}

json-node/src/test/java/io/avaje/json/node/adapter/JsonNodeAdaptersTest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import io.avaje.json.JsonAdapter;
44
import io.avaje.json.JsonReader;
55
import io.avaje.json.node.*;
6+
import io.avaje.json.simple.SimpleMapper;
67
import io.avaje.json.stream.JsonStream;
78
import org.junit.jupiter.api.Test;
89

10+
import java.math.BigDecimal;
911
import java.time.LocalDate;
1012
import java.util.List;
1113

@@ -67,9 +69,15 @@ void create_JsonDouble_expect_sameInstance() {
6769

6870
@Test
6971
void create_JsonDecimal_expect_sameInstance() {
70-
JsonAdapter<?> jsonAdapter = mapper.adapter(JsonDecimal.class);
72+
JsonAdapter<JsonDecimal> jsonAdapter = mapper.adapter(JsonDecimal.class);
7173
JsonAdapter<JsonDecimal> adapter = mapper.adapter(JsonDecimal.class);
7274
assertThat(jsonAdapter).isSameAs(adapter);
75+
76+
SimpleMapper.Type<JsonDecimal> type = mapper.type(jsonAdapter);
77+
78+
String asJson1 = type.toJson(JsonDecimal.of(new BigDecimal("23.45")));
79+
assertThat(asJson1).isEqualTo("23.45");
80+
assertThat(type.fromJson(asJson1)).isEqualTo(JsonDecimal.of(new BigDecimal("23.45")));
7381
}
7482

7583
@Test

0 commit comments

Comments
 (0)