* Note that this mapper is configured to allow single quotes and unquoted field names when parsing JSON strings.
*/
- public static final ObjectMapper MAPPER =
- new ObjectMapper().configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true)
- .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true)
- .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
- .registerModule(new JavaTimeModule());
-
- static {
- MAPPER.getFactory()
- .setStreamReadConstraints(StreamReadConstraints.builder().maxStringLength(25_000_000).build());
- }
+ public static final ObjectMapper MAPPER = JsonMapper.builder(JsonFactory.builderWithJackson2Defaults()
+ .enable(JsonReadFeature.ALLOW_UNQUOTED_PROPERTY_NAMES,
+ JsonReadFeature.ALLOW_SINGLE_QUOTES)
+ .streamReadConstraints(StreamReadConstraints.builder()
+ .maxStringLength(
+ 25_000_000)
+ .build())
+ .build())
+ .configureForJackson2()
+ .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS)
+ .build();
private Json() {
}
@@ -126,7 +128,7 @@ public static ArrayNode createArray(@Nonnull Collection> elements) {
public static ObjectNode parseObject(String json) {
try {
return tryParseObject(json);
- } catch (JsonProcessingException exception) {
+ } catch (JacksonException exception) {
throw Exceptions.handle(LOG, exception);
}
}
@@ -136,9 +138,9 @@ public static ObjectNode parseObject(String json) {
*
* @param json the JSON string to parse
* @return the parsed JSON string as object node
- * @throws JsonProcessingException in case of a malformed JSON string
+ * @throws JacksonException in case of a malformed JSON string
*/
- public static ObjectNode tryParseObject(String json) throws JsonProcessingException {
+ public static ObjectNode tryParseObject(String json) throws JacksonException {
return MAPPER.readValue(json, ObjectNode.class);
}
@@ -151,7 +153,7 @@ public static ObjectNode tryParseObject(String json) throws JsonProcessingExcept
public static ArrayNode parseArray(String json) {
try {
return tryParseArray(json);
- } catch (JsonProcessingException exception) {
+ } catch (JacksonException exception) {
throw Exceptions.handle(LOG, exception);
}
}
@@ -161,9 +163,9 @@ public static ArrayNode parseArray(String json) {
*
* @param json the JSON string to parse
* @return the parsed JSON string as array node
- * @throws JsonProcessingException in case of a malformed JSON string
+ * @throws JacksonException in case of a malformed JSON string
*/
- public static ArrayNode tryParseArray(String json) throws JsonProcessingException {
+ public static ArrayNode tryParseArray(String json) throws JacksonException {
return MAPPER.readValue(json, ArrayNode.class);
}
@@ -176,7 +178,7 @@ public static ArrayNode tryParseArray(String json) throws JsonProcessingExceptio
public static String write(JsonNode objectNode) {
try {
return tryWrite(objectNode);
- } catch (JsonProcessingException exception) {
+ } catch (JacksonException exception) {
throw Exceptions.handle(LOG, exception);
}
}
@@ -186,9 +188,9 @@ public static String write(JsonNode objectNode) {
*
* @param objectNode the node to convert
* @return the JSON string representing the given node
- * @throws JsonProcessingException in case the given node cannot be converted
+ * @throws JacksonException in case the given node cannot be converted
*/
- public static String tryWrite(JsonNode objectNode) throws JsonProcessingException {
+ public static String tryWrite(JsonNode objectNode) throws JacksonException {
return MAPPER.writeValueAsString(objectNode);
}
@@ -201,7 +203,7 @@ public static String tryWrite(JsonNode objectNode) throws JsonProcessingExceptio
public static String writePretty(JsonNode objectNode) {
try {
return tryWritePretty(objectNode);
- } catch (JsonProcessingException exception) {
+ } catch (JacksonException exception) {
throw Exceptions.handle(LOG, exception);
}
}
@@ -211,9 +213,9 @@ public static String writePretty(JsonNode objectNode) {
*
* @param objectNode the node to convert
* @return the pretty printed JSON string representing the given node
- * @throws JsonProcessingException in case the given node cannot be converted
+ * @throws JacksonException in case the given node cannot be converted
*/
- public static String tryWritePretty(JsonNode objectNode) throws JsonProcessingException {
+ public static String tryWritePretty(JsonNode objectNode) throws JacksonException {
return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(objectNode);
}
@@ -532,10 +534,10 @@ public static Optional
+ *
+ */
+ private val accessorTestNode =
+ Json.parseObject("""{ "string": "foo", "number": 42, "numberString": "42", "bool": true, "null": null }""")
+
+ @Test
+ fun `documents string accessor behavior for filled, mismatching, null and missing properties`() {
+ // filled, matching: value is returned
+ assertEquals("foo", accessorTestNode.path("string").stringValue())
+ assertEquals("foo", accessorTestNode.path("string").asString())
+
+ // filled, non-matching: stringValue() is strict, asString() coerces scalars but not containers
+ assertThrows(JsonNodeException::class.java) { accessorTestNode.path("number").stringValue() }
+ assertEquals("42", accessorTestNode.path("number").asString())
+ assertThrows(JsonNodeException::class.java) { accessorTestNode.asString() }
+
+ // JSON null: a legitimate value for stringValue(), an empty string for asString()
+ assertEquals(null, accessorTestNode.path("null").stringValue())
+ assertEquals("", accessorTestNode.path("null").asString())
+
+ // missing: stringValue() throws, asString() coerces leniently
+ assertThrows(JsonNodeException::class.java) { accessorTestNode.path("missing").stringValue() }
+ assertEquals("", accessorTestNode.path("missing").asString())
+
+ // default-taking and Optional variants never throw
+ assertEquals(null, accessorTestNode.path("missing").stringValue(null))
+ assertEquals("fallback", accessorTestNode.path("missing").stringValue("fallback"))
+ assertEquals("foo", accessorTestNode.path("string").stringValueOpt().get())
+ assertTrue(accessorTestNode.path("missing").stringValueOpt().isEmpty)
+ }
+
+ @Test
+ fun `documents numeric accessor behavior for filled, mismatching, null and missing properties`() {
+ // filled, matching: value is returned
+ assertEquals(42L, accessorTestNode.path("number").longValue())
+ assertEquals(42L, accessorTestNode.path("number").asLong())
+
+ // filled, non-matching: longValue() is strict even for numeric strings, asLong() converts
+ // them but throws for non-numeric values
+ assertThrows(JsonNodeException::class.java) { accessorTestNode.path("numberString").longValue() }
+ assertEquals(42L, accessorTestNode.path("numberString").asLong())
+ assertThrows(JsonNodeException::class.java) { accessorTestNode.path("string").asLong() }
+
+ // JSON null and missing: longValue() cannot represent them and throws, asLong() yields 0
+ assertThrows(JsonNodeException::class.java) { accessorTestNode.path("null").longValue() }
+ assertThrows(JsonNodeException::class.java) { accessorTestNode.path("missing").longValue() }
+ assertEquals(0L, accessorTestNode.path("null").asLong())
+ assertEquals(0L, accessorTestNode.path("missing").asLong())
+
+ // default-taking and Optional variants never throw
+ assertEquals(0L, accessorTestNode.path("missing").longValue(0L))
+ assertEquals(-1L, accessorTestNode.path("null").longValue(-1L))
+ assertEquals(42L, accessorTestNode.path("number").longValueOpt().asLong)
+ assertTrue(accessorTestNode.path("null").longValueOpt().isEmpty)
+ assertTrue(accessorTestNode.path("missing").longValueOpt().isEmpty)
+ }
+
+ @Test
+ fun `documents boolean accessor behavior for filled, mismatching, null and missing properties`() {
+ // filled, matching: value is returned
+ assertEquals(true, accessorTestNode.path("bool").booleanValue())
+ assertEquals(true, accessorTestNode.path("bool").asBoolean())
+
+ // filled, non-matching: booleanValue() only accepts actual booleans; asBoolean() converts
+ // "true"/"false" strings and numbers (0 = false, everything else = true) but throws for
+ // other strings
+ assertThrows(JsonNodeException::class.java) { accessorTestNode.path("number").booleanValue() }
+ assertEquals(true, accessorTestNode.path("number").asBoolean())
+ assertThrows(JsonNodeException::class.java) { accessorTestNode.path("string").asBoolean() }
+
+ // JSON null and missing: booleanValue() throws, asBoolean() yields false
+ assertThrows(JsonNodeException::class.java) { accessorTestNode.path("null").booleanValue() }
+ assertThrows(JsonNodeException::class.java) { accessorTestNode.path("missing").booleanValue() }
+ assertEquals(false, accessorTestNode.path("null").asBoolean())
+ assertEquals(false, accessorTestNode.path("missing").asBoolean())
+
+ // default-taking and Optional variants never throw
+ assertEquals(true, accessorTestNode.path("missing").booleanValue(true))
+ assertEquals(false, accessorTestNode.path("null").booleanValue(false))
+ assertEquals(true, accessorTestNode.path("bool").booleanValueOpt().get())
+ assertTrue(accessorTestNode.path("missing").booleanValueOpt().isEmpty)
+ }
+
@Test
fun `Read Amount from POJONode`() {
val inputAmount = Amount.ofRounded(BigDecimal("1.23456789"))