diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index 102607f0d3..10cda9efc6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -1896,6 +1896,24 @@ public ObjectMapper setDefaultPropertyInclusion(JsonInclude.Value incl) { return this; } + /** + * Method for setting default alternative radix that applies to integral types for serialization + * and deserialization of such types as strings. + * This configuration override is applied for all integral properties for which there are no per-type + * or per-property overrides (via annotations or config overrides). + *
+ * NOTE: in Jackson 3.x all configuration goes through {@code ObjectMapper} builders, + * see {@link com.fasterxml.jackson.databind.cfg.MapperBuilder}, + * and this method will be removed from 3.0. + * + * @since 2.21 + */ + public ObjectMapper setDefaultFormat(String radix) { + _configOverrides.setDefaultRadix(radix); + return this; + } + + /** * Short-cut for: *
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java b/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java
index 76f2d9fe1a..e2e66cb524 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java
@@ -184,7 +184,7 @@ public BaseSettings(ClassIntrospector ci, AnnotationIntrospector ai,
_defaultBase64 = defaultBase64;
_typeValidator = ptv;
_accessorNaming = accNaming;
- _cacheProvider = cacheProvider;
+ _cacheProvider = cacheProvider;;
}
/**
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverrides.java b/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverrides.java
index 4961de2386..4fed2bc044 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverrides.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/ConfigOverrides.java
@@ -17,6 +17,7 @@ public class ConfigOverrides
implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
+ private static final String DEFAULT_RADIX = "10";
/**
* Per-type override definitions
@@ -55,6 +56,14 @@ public class ConfigOverrides
*/
protected Boolean _defaultLeniency;
+ /**
+ * Global default radix to apply to an integral type outputted as string. This has the lowest precedence out of all
+ * other methods of enforcing an alternative radix.
+ *
+ * @since 2.21
+ */
+ protected String _defaultRadix;//TODO(Davyd Fridman): Change from string to int once JsonFormat has an actual radix field
+
/*
/**********************************************************************
/* Life cycle
@@ -67,13 +76,32 @@ public ConfigOverrides() {
JsonInclude.Value.empty(),
JsonSetter.Value.empty(),
VisibilityChecker.Std.defaultInstance(),
- null, null
+ null, null, DEFAULT_RADIX
);
}
+ /**
+ * @since 2.21
+ */
+ protected ConfigOverrides(Map, MutableConfigOverride> overrides,
+ JsonInclude.Value defIncl, JsonSetter.Value defSetter,
+ VisibilityChecker> defVisibility, Boolean defMergeable, Boolean defLeniency,
+ String defRadix)
+ {
+ _overrides = overrides;
+ _defaultInclusion = defIncl;
+ _defaultSetterInfo = defSetter;
+ _visibilityChecker = defVisibility;
+ _defaultMergeable = defMergeable;
+ _defaultLeniency = defLeniency;
+ _defaultRadix = defRadix;
+ }
+
/**
* @since 2.10
+ * @deprecated since 2.21
*/
+ @Deprecated
protected ConfigOverrides(Map, MutableConfigOverride> overrides,
JsonInclude.Value defIncl, JsonSetter.Value defSetter,
VisibilityChecker> defVisibility, Boolean defMergeable, Boolean defLeniency)
@@ -197,6 +225,13 @@ public VisibilityChecker> getDefaultVisibility() {
return _visibilityChecker;
}
+ /**
+ * @since 2.21
+ */
+ public String getDefaultRadix() {
+ return _defaultRadix;
+ }
+
/**
* @since 2.9
*/
@@ -232,6 +267,13 @@ public void setDefaultVisibility(VisibilityChecker> v) {
_visibilityChecker = v;
}
+ /**
+ * @since 2.21
+ */
+ public void setDefaultRadix(String v) {
+ this._defaultRadix = v;
+ }
+
/*
/**********************************************************************
/* Helper methods
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperBuilder.java b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperBuilder.java
index cbfe4277d2..cc9f7fa613 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperBuilder.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperBuilder.java
@@ -7,6 +7,7 @@
import java.util.function.Consumer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@@ -747,6 +748,20 @@ public B defaultPropertyInclusion(JsonInclude.Value incl) {
return _this();
}
+ /**
+ * Method for configured default radix to use for serialization/deserialization of integral types as strings.
+ *
+ * @param radix Default radix to use on integral properties
+ *
+ * @return This builder instance to allow call chaining
+ *
+ * @since 2.11
+ */
+ public B defaultFormat(String radix) {
+ _mapper.setDefaultFormat(radix);
+ return _this();
+ }
+
/*
/**********************************************************************
/* Configuring Mix-ins
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java
index 061224e1e5..97a861601c 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java
@@ -507,6 +507,14 @@ public JsonInclude.Value getDefaultInclusion(Class> baseType,
return result;
}
+ /**
+ * Accessor for default radix to apply to integral types when serializing them as string.
+ * The radix obtained from this accessor should have the lowest precedence.
+ *
+ * @since 2.21
+ */
+ public abstract String getDefaultRadix();
+
/**
* Accessor for default format settings to use for serialization (and, to a degree
* deserialization), considering baseline settings and per-type defaults
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java
index 139253571a..b0fcdb3456 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java
@@ -777,6 +777,11 @@ public final JsonInclude.Value getDefaultInclusion(Class> baseType,
return def.withOverrides(v);
}
+ @Override
+ public String getDefaultRadix() {
+ return _configOverrides.getDefaultRadix();
+ }
+
@Override
public final JsonFormat.Value getDefaultPropertyFormat(Class> type) {
return _configOverrides.findFormatDefaults(type);
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringWithRadixToNumberDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringWithRadixToNumberDeserializer.java
new file mode 100644
index 0000000000..bafcb69f12
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringWithRadixToNumberDeserializer.java
@@ -0,0 +1,52 @@
+package com.fasterxml.jackson.databind.deser.std;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+/**
+ * Deserializer used for a string that represents a number in specific radix (base).
+ *
+ * @since 2.21
+ */
+public class FromStringWithRadixToNumberDeserializer
+ extends StdDeserializer {
+ private final int radix;
+
+ public FromStringWithRadixToNumberDeserializer(StdDeserializer> src, int radix) {
+ super(src);
+ this.radix = radix;
+ }
+
+ @Override
+ public Number deserialize(JsonParser p, DeserializationContext ctxt)
+ throws IOException {
+ 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.getText();
+
+ 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
+ }
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java
index 029e36e1ef..4ba35ea868 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java
@@ -5,13 +5,16 @@
import java.math.BigInteger;
import java.util.HashSet;
+import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.NumberInput;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
+import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.ser.std.NumberToStringWithRadixSerializer;
import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.AccessPattern;
import com.fasterxml.jackson.databind.util.ClassUtil;
@@ -26,6 +29,8 @@
public class NumberDeserializers
{
private final static HashSet _classNames = new HashSet();
+ private final static int DEFAULT_RADIX = 10;
+
static {
// note: can skip primitive types; other ways to check them:
Class>[] numberTypes = new Class>[] {
@@ -250,7 +255,7 @@ public Boolean deserializeWithType(JsonParser p, DeserializationContext ctxt,
@JacksonStdImpl
public static class ByteDeserializer
- extends PrimitiveOrWrapperDeserializer
+ extends PrimitiveOrWrapperDeserializer implements ContextualDeserializer
{
private static final long serialVersionUID = 1L;
@@ -274,6 +279,12 @@ public Byte deserialize(JsonParser p, DeserializationContext ctxt) throws IOExce
return _parseByte(p, ctxt);
}
+ @Override
+ public JsonDeserializer> createContextual(DeserializationContext ctxt, BeanProperty property)
+ throws JsonMappingException {
+ return _createRadixStringDeserializer(this, ctxt, property);
+ }
+
protected Byte _parseByte(JsonParser p, DeserializationContext ctxt)
throws IOException
{
@@ -342,7 +353,7 @@ protected Byte _parseByte(JsonParser p, DeserializationContext ctxt)
@JacksonStdImpl
public static class ShortDeserializer
- extends PrimitiveOrWrapperDeserializer
+ extends PrimitiveOrWrapperDeserializer implements ContextualDeserializer
{
private static final long serialVersionUID = 1L;
@@ -367,6 +378,12 @@ public Short deserialize(JsonParser p, DeserializationContext ctxt)
return _parseShort(p, ctxt);
}
+ @Override
+ public JsonDeserializer> createContextual(DeserializationContext ctxt, BeanProperty property)
+ throws JsonMappingException {
+ return _createRadixStringDeserializer(this, ctxt, property);
+ }
+
protected Short _parseShort(JsonParser p, DeserializationContext ctxt)
throws IOException
{
@@ -517,8 +534,7 @@ public Character deserialize(JsonParser p, DeserializationContext ctxt)
@JacksonStdImpl
public final static class IntegerDeserializer
- extends PrimitiveOrWrapperDeserializer
- {
+ extends PrimitiveOrWrapperDeserializer implements ContextualDeserializer {
private static final long serialVersionUID = 1L;
final static IntegerDeserializer primitiveInstance = new IntegerDeserializer(Integer.TYPE, 0);
@@ -557,11 +573,17 @@ public Integer deserializeWithType(JsonParser p, DeserializationContext ctxt,
}
return _parseInteger(p, ctxt, Integer.class);
}
+
+ @Override
+ public JsonDeserializer> createContextual(DeserializationContext ctxt, BeanProperty property)
+ throws JsonMappingException {
+ return _createRadixStringDeserializer(this, ctxt, property);
+ }
}
@JacksonStdImpl
public final static class LongDeserializer
- extends PrimitiveOrWrapperDeserializer
+ extends PrimitiveOrWrapperDeserializer implements ContextualDeserializer
{
private static final long serialVersionUID = 1L;
@@ -586,6 +608,12 @@ public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOExce
}
return _parseLong(p, ctxt, Long.class);
}
+
+ @Override
+ public JsonDeserializer> createContextual(DeserializationContext ctxt, BeanProperty property)
+ throws JsonMappingException {
+ return _createRadixStringDeserializer(this, ctxt, property);
+ }
}
@JacksonStdImpl
@@ -936,7 +964,7 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
@SuppressWarnings("serial")
@JacksonStdImpl
public static class BigIntegerDeserializer
- extends StdScalarDeserializer
+ extends StdScalarDeserializer implements ContextualDeserializer
{
public final static BigIntegerDeserializer instance = new BigIntegerDeserializer();
@@ -1011,6 +1039,12 @@ public BigInteger deserialize(JsonParser p, DeserializationContext ctxt) throws
return (BigInteger) ctxt.handleWeirdStringValue(_valueClass, text,
"not a valid representation");
}
+
+ @Override
+ public JsonDeserializer> createContextual(DeserializationContext ctxt, BeanProperty property)
+ throws JsonMappingException {
+ return _createRadixStringDeserializer(this, ctxt, property);
+ }
}
@SuppressWarnings("serial")
@@ -1089,4 +1123,42 @@ public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt)
"not a valid representation");
}
}
+
+ /**
+ * Method used to create a string deserializer for a number.
+ * If configuration is set properly, we create an alternative radix serializer {@link NumberToStringWithRadixSerializer}.
+ *
+ * @since 2.21
+ */
+ private 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 = Integer.parseInt(format.getPattern());
+ 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) {
+ String pattern = format.getPattern();
+ boolean isInteger = pattern.chars().allMatch(Character::isDigit);
+ if (!isInteger || pattern.isEmpty()) {
+ return false;
+ }
+
+ int radix = Integer.parseInt(pattern);
+ return radix != DEFAULT_RADIX;
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/ConcreteBeanPropertyBase.java b/src/main/java/com/fasterxml/jackson/databind/introspect/ConcreteBeanPropertyBase.java
index 315bb76906..fbe786be56 100644
--- a/src/main/java/com/fasterxml/jackson/databind/introspect/ConcreteBeanPropertyBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/introspect/ConcreteBeanPropertyBase.java
@@ -71,6 +71,7 @@ public final JsonFormat.Value findFormatOverrides(AnnotationIntrospector intr) {
@Override
public JsonFormat.Value findPropertyFormat(MapperConfig> config, Class> baseType)
{
+ JsonFormat.Value v0 = EMPTY_FORMAT.withPattern(config.getDefaultRadix());//TODO(Davyd Fridman): change to withRadix
JsonFormat.Value v1 = config.getDefaultPropertyFormat(baseType);
JsonFormat.Value v2 = null;
AnnotationIntrospector intr = config.getAnnotationIntrospector();
@@ -80,10 +81,18 @@ public JsonFormat.Value findPropertyFormat(MapperConfig> config, Class> base
v2 = intr.findFormat(member);
}
}
- if (v1 == null) {
- return (v2 == null) ? EMPTY_FORMAT : v2;
+
+ JsonFormat.Value formatValue = EMPTY_FORMAT;
+ if (v0 != null) {
+ formatValue = formatValue.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
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java
index 56c2391dae..6e970f88e5 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java
@@ -37,6 +37,8 @@ public class NumberSerializer
protected final boolean _isInt;
+ protected static final int DEFAULT_RADIX = 10;
+
/**
* @since 2.5
*/
@@ -58,7 +60,8 @@ public JsonSerializer> createContextual(SerializerProvider prov,
if (((Class>) handledType()) == BigDecimal.class) {
return bigDecimalAsStringSerializer();
}
- return ToStringSerializer.instance;
+ return createStringSerializer(prov, format, _isInt);
+
default:
}
}
@@ -114,6 +117,35 @@ 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 2.21
+ */
+ public static ToStringSerializerBase createStringSerializer(SerializerProvider prov, JsonFormat.Value format, boolean isInt) {
+ if (isInt && isSerializeWithRadixOverride(format)) {
+ int radix = Integer.parseInt(format.getPattern());
+ 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) {
+ String pattern = format.getPattern();
+ boolean isInteger = pattern.chars().allMatch(Character::isDigit);
+ if (!isInteger || pattern.isEmpty()) {
+ return false;
+ }
+
+ int radix = Integer.parseInt(pattern);
+ return radix != DEFAULT_RADIX;
+ }
+
/**
* @since 2.10
*/
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializers.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializers.java
index be7e956a2f..f5a36006a9 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializers.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializers.java
@@ -106,7 +106,7 @@ public JsonSerializer> createContextual(SerializerProvider prov,
if (((Class>) handledType()) == BigDecimal.class) {
return NumberSerializer.bigDecimalAsStringSerializer();
}
- return ToStringSerializer.instance;
+ return NumberSerializer.createStringSerializer(prov, format, _isInt);
default:
}
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberToStringWithRadixSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberToStringWithRadixSerializer.java
new file mode 100644
index 0000000000..edef76bc23
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberToStringWithRadixSerializer.java
@@ -0,0 +1,67 @@
+package com.fasterxml.jackson.databind.ser.std;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+/**
+ * Serializer used to convert numbers into a representation for a specified radix (base) and serialize
+ * the representation as string.
+ *
+ * @since 2.21
+ */
+@JacksonStdImpl
+public class NumberToStringWithRadixSerializer extends ToStringSerializerBase {
+ private final int radix;
+
+ public NumberToStringWithRadixSerializer(int radix) { super(Object.class);
+ this.radix = radix;
+ }
+
+ public NumberToStringWithRadixSerializer(Class> handledType, int radix) {
+ super(handledType);
+ this.radix = radix;
+ }
+
+ @Override
+ public boolean isEmpty(SerializerProvider prov, Object value) {
+ return false;
+ }
+
+ @Override
+ public void serialize(Object value, JsonGenerator gen, SerializerProvider provider)
+ throws IOException
+ {
+ if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
+ String errorMsg = String.format("To use a custom radix for string serialization, use radix within [%d, %d]", Character.MIN_RADIX, Character.MAX_RADIX);
+ provider.reportBadDefinition(handledType(), errorMsg);
+ }
+
+ String text = "";
+ if (value instanceof BigInteger) {
+ BigInteger bigIntegerValue = (BigInteger) value;
+ text = bigIntegerValue.toString(radix);
+ } else if (value instanceof Byte
+ || value instanceof Short
+ || value instanceof Integer
+ || value instanceof Long) {
+ long longValue = ((Number) value).longValue();
+ text = Long.toString(longValue, radix);
+ } else {
+ provider.reportBadDefinition(handledType(),
+ "Trying to serialize a non-whole number with NumberToStringWithRadixSerializer");
+ }
+
+ gen.writeString(text);
+
+ }
+
+ @Override
+ public String valueToString(Object value) {
+ // should never be called
+ throw new IllegalStateException();
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/SerializeUsingJDKTest.java b/src/test/java/com/fasterxml/jackson/databind/SerializeUsingJDKTest.java
index d607fc4f76..f8943f39c7 100644
--- a/src/test/java/com/fasterxml/jackson/databind/SerializeUsingJDKTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/SerializeUsingJDKTest.java
@@ -238,6 +238,8 @@ public void testTypeFactory() throws Exception
public void testObjectReaderSerializationWithPolymorphism()
throws Exception
{
+ Properties props = System.getProperties();
+ props.setProperty("sun.io.serialization.extendedDebugInfo", "true");
Class>[] classes = new Class>[] {
FooClass.class,
FooDeduction.class,
diff --git a/src/test/java/com/fasterxml/jackson/databind/format/DifferentRadixNumberFormatTest.java b/src/test/java/com/fasterxml/jackson/databind/format/DifferentRadixNumberFormatTest.java
new file mode 100644
index 0000000000..4ab8783d05
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/format/DifferentRadixNumberFormatTest.java
@@ -0,0 +1,216 @@
+package com.fasterxml.jackson.databind.format;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigInteger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class DifferentRadixNumberFormatTest extends DatabindTestUtil {
+
+ private static final String HEX_RADIX = "16";
+ public static final String BINARY_RADIX = "2";
+
+ private static class IntegerWrapper {
+ public Integer value;
+
+ public IntegerWrapper() {}
+ public IntegerWrapper(Integer v) { value = v; }
+ }
+
+ private static class IntWrapper {
+ public int value;
+
+ public IntWrapper() {}
+ public IntWrapper(int v) { value = v; }
+ }
+
+ private static class AnnotatedMethodIntWrapper {
+ private int value;
+
+ public AnnotatedMethodIntWrapper() {
+ }
+ public AnnotatedMethodIntWrapper(int v) {
+ value = v;
+ }
+
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HEX_RADIX)
+ public int getValue() {
+ return value;
+ }
+ }
+
+ private static class IncorrectlyAnnotatedMethodIntWrapper {
+ private int value;
+
+ public IncorrectlyAnnotatedMethodIntWrapper() {
+ }
+ public IncorrectlyAnnotatedMethodIntWrapper(int v) {
+ value = v;
+ }
+
+ @JsonFormat(shape = JsonFormat.Shape.STRING)
+ public int getValue() {
+ return value;
+ }
+ }
+
+ private static class AllIntegralTypeWrapper {
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = BINARY_RADIX)
+ public byte byteValue;
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = BINARY_RADIX)
+ public Byte ByteValue;
+
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = BINARY_RADIX)
+ public short shortValue;
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = BINARY_RADIX)
+ public Short ShortValue;
+
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = BINARY_RADIX)
+ public int intValue;
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = BINARY_RADIX)
+ public Integer IntegerValue;
+
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = BINARY_RADIX)
+ public long longValue;
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = BINARY_RADIX)
+ public Long LongValue;
+
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = BINARY_RADIX)
+ public BigInteger bigInteger;
+
+ public AllIntegralTypeWrapper() {
+ }
+
+ public AllIntegralTypeWrapper(byte byteValue, Byte ByteValue, short shortValue, Short ShortValue, int intValue,
+ Integer IntegerValue, long longValue, Long LongValue, BigInteger bigInteger) {
+ this.byteValue = byteValue;
+ this.ByteValue = ByteValue;
+ this.shortValue = shortValue;
+ this.ShortValue = ShortValue;
+ this.intValue = intValue;
+ this.IntegerValue = IntegerValue;
+ this.longValue = longValue;
+ this.LongValue = LongValue;
+ this.bigInteger = bigInteger;
+ }
+ }
+
+ @Test
+ void testIntegerSerializedAsHexString()
+ throws JsonProcessingException {
+ ObjectMapper mapper = newJsonMapper();
+ mapper.configOverride(Integer.class).setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING).withPattern(HEX_RADIX));
+ IntegerWrapper initialIntegerWrapper = new IntegerWrapper(10);
+ String json = mapper.writeValueAsString(initialIntegerWrapper);
+ String expectedJson = "{'value':'a'}";
+
+ assertEquals(a2q(expectedJson), json);
+
+ IntegerWrapper readBackIntegerWrapper = mapper.readValue(a2q(expectedJson), IntegerWrapper.class);
+
+ assertNotNull(readBackIntegerWrapper);
+ assertEquals(initialIntegerWrapper.value, readBackIntegerWrapper.value);
+ }
+
+
+ @Test
+ void testIntSerializedAsHexString()
+ throws JsonProcessingException {
+ ObjectMapper mapper = newJsonMapper();
+ mapper.configOverride(int.class)
+ .setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING).withPattern(HEX_RADIX));
+ IntWrapper intialIntWrapper = new IntWrapper(10);
+ String expectedJson = "{'value':'a'}";
+
+ String json = mapper.writeValueAsString(intialIntWrapper);
+
+ assertEquals(a2q(expectedJson), json);
+
+ IntWrapper readBackIntWrapper = mapper.readValue(a2q(expectedJson), IntWrapper.class);
+
+ assertNotNull(readBackIntWrapper);
+ assertEquals(intialIntWrapper.value, readBackIntWrapper.value);
+
+ }
+
+ @Test
+ void testAnnotatedAccessorSerializedAsHexString()
+ throws JsonProcessingException {
+ ObjectMapper mapper = newJsonMapper();
+ AnnotatedMethodIntWrapper initialIntWrapper = new AnnotatedMethodIntWrapper(10);
+ String expectedJson = "{'value':'a'}";
+
+ String json = mapper.writeValueAsString(initialIntWrapper);
+
+ assertEquals(a2q(expectedJson), json);
+
+ AnnotatedMethodIntWrapper readBackIntWrapper = mapper.readValue(a2q(expectedJson), AnnotatedMethodIntWrapper.class);
+
+ assertNotNull(readBackIntWrapper);
+ assertEquals(initialIntWrapper.value, readBackIntWrapper.value);
+ }
+
+ @Test
+ void testAnnotatedAccessorWithoutPatternDoesNotThrow()
+ throws JsonProcessingException {
+ ObjectMapper mapper = newJsonMapper();
+ IncorrectlyAnnotatedMethodIntWrapper initialIntWrapper = new IncorrectlyAnnotatedMethodIntWrapper(10);
+ String expectedJson = "{'value':'10'}";
+
+ String json = mapper.writeValueAsString(initialIntWrapper);
+
+ assertEquals(a2q(expectedJson), json);
+ }
+
+ @Test
+ void testUsingDefaultConfigOverrideRadixToSerializeAsHexString()
+ throws JsonProcessingException {
+ ObjectMapper mapper = newJsonMapper();
+ mapper.configOverride(Integer.class)
+ .setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING));
+ mapper.setDefaultFormat(HEX_RADIX);
+ IntegerWrapper intialIntegerWrapper = new IntegerWrapper(10);
+ String expectedJson = "{'value':'a'}";
+
+ String json = mapper.writeValueAsString(intialIntegerWrapper);
+
+ assertEquals(a2q(expectedJson), json);
+
+ IntegerWrapper readBackIntegerWrapper = mapper.readValue(a2q(expectedJson), IntegerWrapper.class);
+
+ assertNotNull(readBackIntegerWrapper);
+ assertEquals(intialIntegerWrapper.value, readBackIntegerWrapper.value);
+ }
+
+ @Test
+ void testAllIntegralTypesGetSerializedAsBinary()
+ throws JsonProcessingException {
+ ObjectMapper mapper = newJsonMapper();
+ AllIntegralTypeWrapper initialIntegralTypeWrapper = new AllIntegralTypeWrapper((byte) 1,
+ (byte) 2, (short) 3, (short) 4, 5, 6, 7L, 8L, new BigInteger("9"));
+ String expectedJson = "{'byteValue':'1','ByteValue':'10','shortValue':'11','ShortValue':'100','intValue':'101','IntegerValue':'110','longValue':'111','LongValue':'1000','bigInteger':'1001'}";
+
+ String json = mapper.writeValueAsString(initialIntegralTypeWrapper);
+
+ assertEquals(a2q(expectedJson), json);
+
+ AllIntegralTypeWrapper readbackIntegralTypeWrapper = mapper.readValue(a2q(expectedJson), AllIntegralTypeWrapper.class);
+
+ assertNotNull(readbackIntegralTypeWrapper);
+ assertEquals(initialIntegralTypeWrapper.byteValue, readbackIntegralTypeWrapper.byteValue);
+ assertEquals(initialIntegralTypeWrapper.ByteValue, readbackIntegralTypeWrapper.ByteValue);
+ assertEquals(initialIntegralTypeWrapper.shortValue, readbackIntegralTypeWrapper.shortValue);
+ assertEquals(initialIntegralTypeWrapper.ShortValue, readbackIntegralTypeWrapper.ShortValue);
+ assertEquals(initialIntegralTypeWrapper.intValue, readbackIntegralTypeWrapper.intValue);
+ assertEquals(initialIntegralTypeWrapper.IntegerValue, readbackIntegralTypeWrapper.IntegerValue);
+ assertEquals(initialIntegralTypeWrapper.longValue, readbackIntegralTypeWrapper.longValue);
+ assertEquals(initialIntegralTypeWrapper.LongValue, readbackIntegralTypeWrapper.LongValue);
+ assertEquals(initialIntegralTypeWrapper.bigInteger, readbackIntegralTypeWrapper.bigInteger);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/ConcreteBeanPropertyBaseTest.java b/src/test/java/com/fasterxml/jackson/databind/introspect/ConcreteBeanPropertyBaseTest.java
new file mode 100644
index 0000000000..f7fe25d607
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/introspect/ConcreteBeanPropertyBaseTest.java
@@ -0,0 +1,181 @@
+package com.fasterxml.jackson.databind.introspect;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.AnnotationIntrospector;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.PropertyMetadata;
+import com.fasterxml.jackson.databind.PropertyName;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.cfg.MapperConfig;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Member;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+//TODO: Once mockito is updated to include Premain-Class in its MANIFEST.MF, we need to add -javaagent:/${m2directory}/.m2/repository/org/mockito/mockito-core/${mockit-version}/$33{mockit-version}.jar
+class ConcreteBeanPropertyBaseTest {
+
+ private static final class TestConcreteBeanPropertyBase extends ConcreteBeanPropertyBase {
+
+ TestConcreteBeanPropertyBase(PropertyMetadata md) {
+ super(md);
+ }
+
+ @Override
+ public String getName() {
+ return "";
+ }
+
+ @Override
+ public PropertyName getFullName() {
+ return null;
+ }
+
+ @Override
+ public JavaType getType() {
+ return null;
+ }
+
+ @Override
+ public PropertyName getWrapperName() {
+ return null;
+ }
+
+ @Override
+ public A getAnnotation(Class acls) {
+ return null;
+ }
+
+ @Override
+ public A getContextAnnotation(Class acls) {
+ return null;
+ }
+
+ @Override
+ public AnnotatedMember getMember() {
+ return new TestAnnotatedMember(null, null);
+ }
+
+ @Override
+ public void depositSchemaProperty(JsonObjectFormatVisitor objectVisitor, SerializerProvider provider)
+ throws JsonMappingException {
+
+ }
+ }
+
+ private static final class TestAnnotatedMember extends AnnotatedMember {
+
+ TestAnnotatedMember(TypeResolutionContext ctxt, AnnotationMap annotations) {
+ super(ctxt, annotations);
+ }
+
+ @Override
+ public Annotated withAnnotations(AnnotationMap fallback) {
+ return null;
+ }
+
+ @Override
+ public Class> getDeclaringClass() {
+ return null;
+ }
+
+ @Override
+ public Member getMember() {
+ return null;
+ }
+
+ @Override
+ public void setValue(Object pojo, Object value)
+ throws UnsupportedOperationException, IllegalArgumentException {
+
+ }
+
+ @Override
+ public Object getValue(Object pojo)
+ throws UnsupportedOperationException, IllegalArgumentException {
+ return null;
+ }
+
+ @Override
+ public AnnotatedElement getAnnotated() {
+ return null;
+ }
+
+ @Override
+ protected int getModifiers() {
+ return 0;
+ }
+
+ @Override
+ public String getName() {
+ return "";
+ }
+
+ @Override
+ public JavaType getType() {
+ return null;
+ }
+
+ @Override
+ public Class> getRawType() {
+ return null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+ }
+
+ private TestConcreteBeanPropertyBase testConcreteBeanProperty;
+ private Class someType;
+ private MapperConfig> mapperConfig;
+ private AnnotationIntrospector annotationIntrospector;
+
+ @BeforeEach
+ void setUp() {
+ mapperConfig = mock(MapperConfig.class);
+ testConcreteBeanProperty = new TestConcreteBeanPropertyBase(
+ PropertyMetadata.STD_REQUIRED);
+ annotationIntrospector = mock(AnnotationIntrospector.class);
+ when(mapperConfig.getAnnotationIntrospector()).thenReturn(annotationIntrospector);
+ someType = Class.class;
+ }
+
+ @Test
+ void testFormatPrecedenceIsFollowed() {
+ String lowestPrecedenceFormat = "Low Precedence";
+ JsonFormat.Value midPrecedenceFormat = new JsonFormat.Value("Mid Precedence", null,
+ (String) null, null, null, null);
+ JsonFormat.Value highestPrecedence = new JsonFormat.Value("High Precedence", null,
+ (String) null, null, null, null);
+ when(mapperConfig.getDefaultRadix()).thenReturn(lowestPrecedenceFormat);
+ when(mapperConfig.getDefaultPropertyFormat(any())).thenReturn(midPrecedenceFormat);
+ when(annotationIntrospector.findFormat(any())).thenReturn(highestPrecedence);
+
+ JsonFormat.Value resultFormat = testConcreteBeanProperty.findPropertyFormat(mapperConfig, someType);
+
+
+ assertEquals(highestPrecedence.getPattern(), resultFormat.getPattern());
+ }
+}
\ No newline at end of file