Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,9 @@
</dependency>
<!-- Changelog: https://github.com/FasterXML/jackson/wiki/Jackson-Releases -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<!-- Changelog: https://commons.apache.org/proper/commons-lang/changes.html -->
<dependency>
<groupId>org.apache.commons</groupId>
Expand Down
39 changes: 39 additions & 0 deletions src/main/java/sirius/kernel/commons/Amount.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import sirius.kernel.nls.NLS;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;
import tools.jackson.databind.annotation.JsonSerialize;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
Expand Down Expand Up @@ -46,6 +51,7 @@
/// @see NumberFormat
/// @see BigDecimal
@Immutable
@JsonSerialize(using = Amount.JacksonSerializer.class)
public class Amount extends Number implements Comparable<Amount> {

@Serial
Expand Down Expand Up @@ -262,6 +268,11 @@ public BigDecimal fetchAmountWithoutTrailingZeros() {
/// Unwraps the internally used <tt>BigDecimal</tt> with rounding like in [#toMachineString()] applied.
/// This is used for Jackson Object Mapping.
///
/// Note that Jackson 3 ignores `@JsonValue` on subclasses of [Number] (the standard number serializer takes
/// precedence), therefore [JacksonSerializer] is registered via `@JsonSerialize` on the class, which is consulted
/// before the standard handling. The annotation is kept for documentation purposes and in case Jackson restores its
/// precedence.
///
/// @return the internally used <tt>BigDecimal</tt> with rounding applied
@Nullable
@JsonValue
Expand All @@ -273,6 +284,34 @@ private BigDecimal getRoundedAmount() {
}
}

/// Serializes an [Amount] as its rounded <tt>BigDecimal</tt> value (or <tt>null</tt> for [#NOTHING]), replicating
/// the `@JsonValue` behavior of Jackson 2 which no longer applies to subclasses of [Number] in Jackson 3.
public static class JacksonSerializer extends ValueSerializer<Amount> {
/**
* @param value Value to serialize; can <b>not</b> be null.
* @param generator Generator used to output resulting JSON content
* @param context Context that can be used to get serializers for
* serializing Objects value contains, if any.
* @throws JacksonException when the serialization fails
*/
@Override
public void serialize(Amount value, JsonGenerator generator, SerializationContext context) {
BigDecimal roundedAmount = value.getRoundedAmount();
if (roundedAmount == null) {
generator.writeNull();
} else {
generator.writeNumber(roundedAmount);
}
}

/// Considers [#NOTHING] as empty, so that `@JsonInclude(NON_EMPTY)` suppresses empty amounts
/// like the `@JsonValue` based serialization of Jackson 2 did.
@Override
public boolean isEmpty(SerializationContext context, Amount value) {
return value == null || value.isEmpty();
}
}

@Override
public int intValue() {
throwExceptionIfEmpty();
Expand Down
88 changes: 45 additions & 43 deletions src/main/java/sirius/kernel/commons/Json.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,21 @@

package sirius.kernel.commons;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.StreamReadConstraints;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.POJONode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import sirius.kernel.health.Exceptions;
import sirius.kernel.health.Log;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonPointer;
import tools.jackson.core.StreamReadConstraints;
import tools.jackson.core.json.JsonFactory;
import tools.jackson.core.json.JsonReadFeature;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.cfg.DateTimeFeature;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.node.ArrayNode;
import tools.jackson.databind.node.ObjectNode;
import tools.jackson.databind.node.POJONode;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -57,16 +58,17 @@ public class Json {
* <p>
* 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() {
}
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -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);
}

Expand All @@ -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);
}
}
Expand All @@ -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);
}

Expand All @@ -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);
}
}
Expand All @@ -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);
}

Expand All @@ -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);
}
}
Expand All @@ -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);
}

Expand Down Expand Up @@ -532,10 +534,10 @@ public static Optional<String> tryValueString(@Nonnull JsonNode jsonNode, String
return Optional.empty();
}
if (node.isNumber() || node.isBoolean()) {
return Optional.of(node.asText());
return Optional.of(node.asString());
}
if (node.isTextual()) {
return Optional.of(node.textValue());
if (node.isString()) {
return Optional.of(node.stringValue());
}
return Optional.empty();
}
Expand All @@ -557,8 +559,8 @@ public static Amount getValueAmount(@Nonnull JsonNode jsonNode, String fieldName
if (node.isNumber()) {
return Amount.ofRounded(node.decimalValue());
}
if (node.isTextual()) {
return Amount.ofMachineString(node.textValue());
if (node.isString()) {
return Amount.ofMachineString(node.stringValue());
}
if (node.isPojo()) {
return tryGetFromPojoNode(node, Amount.class).orElse(Amount.NOTHING);
Expand All @@ -580,11 +582,11 @@ public static Optional<LocalDate> tryValueDate(@Nonnull JsonNode jsonNode, Strin
if (node != null && node.isPojo()) {
return tryGetFromPojoNode(node, LocalDate.class);
}
if (node == null || node.isNull() || !node.isTextual()) {
if (node == null || node.isNull() || !node.isString()) {
return Optional.empty();
}
try {
return Optional.of(LocalDate.parse(node.textValue(), DateTimeFormatter.ISO_DATE));
return Optional.of(LocalDate.parse(node.stringValue(), DateTimeFormatter.ISO_DATE));
} catch (DateTimeParseException exception) {
return Optional.empty();
}
Expand All @@ -605,11 +607,11 @@ public static Optional<LocalDateTime> tryValueDateTime(@Nonnull JsonNode jsonNod
if (node != null && node.isPojo()) {
return tryGetFromPojoNode(node, LocalDateTime.class);
}
if (node == null || node.isNull() || !node.isTextual()) {
if (node == null || node.isNull() || !node.isString()) {
return Optional.empty();
}
try {
return Optional.of(LocalDateTime.parse(node.textValue(), DateTimeFormatter.ISO_DATE_TIME));
return Optional.of(LocalDateTime.parse(node.stringValue(), DateTimeFormatter.ISO_DATE_TIME));
} catch (DateTimeParseException exception) {
return Optional.empty();
}
Expand Down
Loading
Loading