Skip to content
Open
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
43 changes: 38 additions & 5 deletions src/main/java/tools/jackson/databind/cfg/ConfigOverrides.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ public class ConfigOverrides

protected Boolean _defaultLeniency;

/**
* @since 3.1
*/
protected JsonFormat.Value _defaultFormat;

/*
/**********************************************************************
/* Life cycle
Expand All @@ -64,20 +69,32 @@ public ConfigOverrides() {
INCLUDE_DEFAULT,
JsonSetter.Value.empty(),
DEFAULT_VISIBILITY_CHECKER,
null, null
null, null, JsonFormat.Value.empty()
);
}

protected ConfigOverrides(Map<Class<?>, MutableConfigOverride> overrides,
JsonInclude.Value defIncl, JsonSetter.Value defSetter,
VisibilityChecker defVisibility,
Boolean defMergeable, Boolean defLeniency) {
JsonInclude.Value defIncl, JsonSetter.Value defSetter,
VisibilityChecker defVisibility,
Boolean defMergeable, Boolean defLeniency, JsonFormat.Value defFormat) {
_overrides = overrides;
_defaultInclusion = defIncl;
_defaultNullHandling = defSetter;
_visibilityChecker = defVisibility;
_defaultMergeable = defMergeable;
_defaultLeniency = defLeniency;
_defaultFormat = defFormat;
}

@Deprecated
/*
* @deprecated since 3.1
*/
protected ConfigOverrides(Map<Class<?>, MutableConfigOverride> overrides,
JsonInclude.Value defIncl, JsonSetter.Value defSetter,
VisibilityChecker defVisibility,
Boolean defMergeable, Boolean defLeniency) {
this(overrides, defIncl, defSetter, defVisibility, defMergeable, defLeniency, JsonFormat.Value.empty());
}

@Override
Expand All @@ -94,7 +111,7 @@ public ConfigOverrides snapshot()
}
return new ConfigOverrides(newOverrides,
_defaultInclusion, _defaultNullHandling, _visibilityChecker,
_defaultMergeable, _defaultLeniency);
_defaultMergeable, _defaultLeniency, _defaultFormat);
}

/*
Expand Down Expand Up @@ -176,6 +193,14 @@ public VisibilityChecker getDefaultVisibility() {
return _visibilityChecker;
}


/**
* @since 3.1
*/
public JsonFormat.Value getDefaultFormat() {
return _defaultFormat;
}

/**
* Alternate accessor needed due to complexities of Record
* auto-discovery: needs to obey custom overrides but also
Expand Down Expand Up @@ -222,6 +247,14 @@ public ConfigOverrides setDefaultVisibility(VisibilityChecker v) {
return this;
}

/**
* @since 3.1
*/
public ConfigOverrides setDefaultFormat(JsonFormat.Value format) {
this._defaultFormat = format;
return this;
}

public ConfigOverrides setDefaultVisibility(JsonAutoDetect.Value vis) {
_visibilityChecker = VisibilityChecker.construct(vis);
return this;
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/tools/jackson/databind/cfg/MapperBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.function.Consumer;
import java.util.function.UnaryOperator;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
Expand Down Expand Up @@ -947,6 +948,16 @@ public B changeDefaultPropertyInclusion(UnaryOperator<JsonInclude.Value> handler
return _this();
}

/**
* Method for configuring default format to use for serialization/deserialization.
*
* @since 3.1
*/
public B defaultFormat(JsonFormat.Value format) {
_configOverrides.setDefaultFormat(format);
return _this();
}

/**
* Method for changing currently default settings for handling of `null` values during
* deserialization, regarding whether they are set as-is, ignored completely, or possible
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/tools/jackson/databind/cfg/MapperConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,15 @@ public JsonInclude.Value getDefaultInclusion(Class<?> baseType,
return result;
}


/**
* Accessor for default format to apply for serialization.
* The format obtained from this accessor should have the lowest precedence.
*
* @since 3.1
*/
public abstract JsonFormat.Value getDefaultFormat();

/**
* Accessor for default format settings to use for serialization (and, to a degree
* deserialization), considering baseline settings and per-type defaults
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,11 @@ public final JsonInclude.Value getDefaultInclusion(Class<?> baseType,
return def.withOverrides(v);
}

@Override
public JsonFormat.Value getDefaultFormat() {
return _configOverrides.getDefaultFormat();
}

@Override
public final JsonFormat.Value getDefaultPropertyFormat(Class<?> type) {
return _configOverrides.findFormatDefaults(type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import tools.jackson.databind.util.AccessPattern;
import tools.jackson.databind.util.ClassUtil;

import static tools.jackson.databind.deser.std.RadixSerializerCreator.createRadixStringDeserializer;

/**
* Container class for deserializers that handle core JDK primitive
* (and matching wrapper) types, as well as standard "big" numeric types.
Expand Down Expand Up @@ -261,6 +263,16 @@ public Byte deserialize(JsonParser p, DeserializationContext ctxt) throws Jackso
return _parseByte(p, ctxt);
}


/**
* @since 3.1
*/
@Override
public ValueDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
{
return createRadixStringDeserializer(this, ctxt, property);
}

protected Byte _parseByte(JsonParser p, DeserializationContext ctxt)
throws JacksonException
{
Expand Down Expand Up @@ -346,6 +358,15 @@ public ShortDeserializer(Class<Short> cls, Short nvl)
super(cls, LogicalType.Integer, nvl, (short)0);
}

/**
* @since 3.1
*/
@Override
public ValueDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
{
return createRadixStringDeserializer(this, ctxt, property);
}

@Override
public Short deserialize(JsonParser p, DeserializationContext ctxt)
throws JacksonException
Expand Down Expand Up @@ -526,6 +547,15 @@ public IntegerDeserializer(Class<Integer> cls, Integer nvl) {
@Override
public boolean isCachable() { return true; }

/**
* @since 3.1
*/
@Override
public ValueDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
{
return createRadixStringDeserializer(this, ctxt, property);
}

@Override
public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
if (p.isExpectedNumberIntToken()) {
Expand Down Expand Up @@ -567,6 +597,15 @@ public LongDeserializer(Class<Long> cls, Long nvl) {
@Override
public boolean isCachable() { return true; }

/**
* @since 3.1
*/
@Override
public ValueDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
{
return createRadixStringDeserializer(this, ctxt, property);
}

@Override
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
if (p.isExpectedNumberIntToken()) {
Expand Down Expand Up @@ -937,6 +976,15 @@ public final LogicalType logicalType() {
return LogicalType.Integer;
}

/**
* @since 3.1
*/
@Override
public ValueDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
{
return createRadixStringDeserializer(this, ctxt, property);
}

@Override
public BigInteger deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package tools.jackson.databind.deser.std;

import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;
import tools.jackson.databind.DeserializationContext;

import java.math.BigInteger;

/**
* Deserializer used for a string that represents a number in specific radix (base).
*
* @since 3.1
*/
public class FromStringWithRadixToNumberDeserializer
extends StdDeserializer<Number> {
private final int radix;

public FromStringWithRadixToNumberDeserializer(StdDeserializer<?> src, int radix) {
super(src);
this.radix = radix;
}

@Override
public Number deserialize(JsonParser p, DeserializationContext ctxt) {
Class<?> handledType = handledType();

if (p.currentToken() != JsonToken.VALUE_STRING) {
ctxt.reportInputMismatch(handledType,
"Read something other than string when deserializing a value using FromStringWithRadixToNumberDeserializer.");
}

String text = p.getString();

if (handledType.equals(BigInteger.class)) {
return new BigInteger(text, radix);
} else if (handledType.equals(byte.class) || handledType.equals(Byte.class)) {
return Byte.parseByte(text, radix);
} else if (handledType.equals(short.class) || handledType.equals(Short.class)) {
return Short.parseShort(text, radix);
} else if (handledType.equals(int.class) || handledType.equals(Integer.class)) {
return Integer.parseInt(text, radix);
} else if (handledType.equals(long.class) || handledType.equals(Long.class)) {
return Long.parseLong(text, radix);
} else {
ctxt.reportInputMismatch(handledType,
"Trying to deserialize a non-whole number with NumberToStringWithRadixSerializer");
return null;//should not reach here
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package tools.jackson.databind.deser.std;

import com.fasterxml.jackson.annotation.JsonFormat;
import tools.jackson.databind.BeanProperty;
import tools.jackson.databind.DeserializationContext;

import static com.fasterxml.jackson.annotation.JsonFormat.DEFAULT_RADIX;

/**
* Factory class for {@link FromStringWithRadixToNumberDeserializer} for deserializers in {@link tools.jackson.databind.deser.jdk.NumberDeserializers}
* @since 3.1
*
*/
public class RadixSerializerCreator {
public static StdDeserializer<? extends Number> createRadixStringDeserializer(
StdScalarDeserializer<? extends Number> initialDeser,
DeserializationContext ctxt, BeanProperty property)
{
JsonFormat.Value format = initialDeser.findFormatOverrides(ctxt, property, initialDeser.handledType());

if (format == null || format.getShape() != JsonFormat.Shape.STRING) {
return initialDeser;
}

if (isSerializeWithRadixOverride(format)) {
int radix = format.getRadix();
return new FromStringWithRadixToNumberDeserializer(initialDeser, radix);
}

return initialDeser;
}

/**
* Check if we have a proper {@link JsonFormat} annotation for serializing a number
* using an alternative radix specified in the annotation.
*/
private static boolean isSerializeWithRadixOverride(JsonFormat.Value format) {
return format.hasNonDefaultRadix();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,18 @@ public JsonFormat.Value findFormatOverrides(MapperConfig<?> config) {
@Override
public JsonFormat.Value findPropertyFormat(MapperConfig<?> config, Class<?> baseType)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am bit confused here -- why are changes needed? Shouldn't override handling work for radix just like all other properties? If not, why not (are fixes needed in jackson-annotations?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason is to consider default format out of ConfigOverrides which has the lowest precedence. This way we can make the a given radix apply to all integral types. This is what you meant here, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also added a test for this behavior DifferentRadixNumberFormatTest#testIntSerializedAsHexStringWithDefaultRadix

{
JsonFormat.Value v0 = config.getDefaultFormat();
JsonFormat.Value v1 = config.getDefaultPropertyFormat(baseType);
JsonFormat.Value v2 = findFormatOverrides(config);
if (v1 == null) {
return (v2 == null) ? EMPTY_FORMAT : v2;

JsonFormat.Value formatValue = EMPTY_FORMAT.withOverrides(v0);
if (v1 != null) {
formatValue = formatValue.withOverrides(v1);
}
if (v2 != null) {
formatValue = formatValue.withOverrides(v2);
}
return (v2 == null) ? v1 : v1.withOverrides(v2);
return formatValue;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import tools.jackson.databind.ser.std.ToStringSerializer;
import tools.jackson.databind.ser.std.ToStringSerializerBase;

import static com.fasterxml.jackson.annotation.JsonFormat.DEFAULT_RADIX;

/**
* As a fallback, we may need to use this serializer for other
* types of {@link Number}s: both custom types and "big" numbers
Expand Down Expand Up @@ -58,7 +60,7 @@ public ValueSerializer<?> createContextual(SerializationContext prov,
if (((Class<?>) handledType()) == BigDecimal.class) {
return bigDecimalAsStringSerializer();
}
return ToStringSerializer.instance;
return NumberSerializer.createStringSerializer(prov, format, _isInt);
default:
}
}
Expand Down Expand Up @@ -105,6 +107,28 @@ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType t
}
}

/**
* Method used to create a string serializer for a number. If the number is integer, and configuration is set properly,
* we create an alternative radix serializer {@link NumberToStringWithRadixSerializer}.
*
* @since 3.1
*/
public static ToStringSerializerBase createStringSerializer(SerializationContext ctxt, JsonFormat.Value format, boolean isInt) {
if (isInt && isSerializeWithRadixOverride(format)) {
int radix = format.getRadix();
return new NumberToStringWithRadixSerializer(radix);
}
return ToStringSerializer.instance;
}

/**
* Check if we have a proper {@link JsonFormat} annotation for serializing a number
* using an alternative radix specified in the annotation.
*/
private static boolean isSerializeWithRadixOverride(JsonFormat.Value format) {
return format.hasNonDefaultRadix();
}

/**
* @since 2.10
*/
Expand Down
Loading