diff --git a/src/main/java/tools/jackson/databind/cfg/ConfigOverrides.java b/src/main/java/tools/jackson/databind/cfg/ConfigOverrides.java index 24b5ab0426..e702acb764 100644 --- a/src/main/java/tools/jackson/databind/cfg/ConfigOverrides.java +++ b/src/main/java/tools/jackson/databind/cfg/ConfigOverrides.java @@ -53,6 +53,11 @@ public class ConfigOverrides protected Boolean _defaultLeniency; + /** + * @since 3.1 + */ + protected JsonFormat.Value _defaultFormat; + /* /********************************************************************** /* Life cycle @@ -64,20 +69,32 @@ public ConfigOverrides() { INCLUDE_DEFAULT, JsonSetter.Value.empty(), DEFAULT_VISIBILITY_CHECKER, - null, null + null, null, JsonFormat.Value.empty() ); } protected ConfigOverrides(Map, 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, 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 @@ -94,7 +111,7 @@ public ConfigOverrides snapshot() } return new ConfigOverrides(newOverrides, _defaultInclusion, _defaultNullHandling, _visibilityChecker, - _defaultMergeable, _defaultLeniency); + _defaultMergeable, _defaultLeniency, _defaultFormat); } /* @@ -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 @@ -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; diff --git a/src/main/java/tools/jackson/databind/cfg/MapperBuilder.java b/src/main/java/tools/jackson/databind/cfg/MapperBuilder.java index 2247acc9b3..26eebb815e 100644 --- a/src/main/java/tools/jackson/databind/cfg/MapperBuilder.java +++ b/src/main/java/tools/jackson/databind/cfg/MapperBuilder.java @@ -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; @@ -947,6 +948,16 @@ public B changeDefaultPropertyInclusion(UnaryOperator 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 diff --git a/src/main/java/tools/jackson/databind/cfg/MapperConfig.java b/src/main/java/tools/jackson/databind/cfg/MapperConfig.java index 040227b2ab..56b7140430 100644 --- a/src/main/java/tools/jackson/databind/cfg/MapperConfig.java +++ b/src/main/java/tools/jackson/databind/cfg/MapperConfig.java @@ -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 diff --git a/src/main/java/tools/jackson/databind/cfg/MapperConfigBase.java b/src/main/java/tools/jackson/databind/cfg/MapperConfigBase.java index 0f71834ce4..1768a1db0e 100644 --- a/src/main/java/tools/jackson/databind/cfg/MapperConfigBase.java +++ b/src/main/java/tools/jackson/databind/cfg/MapperConfigBase.java @@ -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); diff --git a/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java b/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java index ce2303f14c..93b99a6278 100644 --- a/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java +++ b/src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java @@ -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. @@ -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 { @@ -346,6 +358,15 @@ public ShortDeserializer(Class 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 @@ -526,6 +547,15 @@ public IntegerDeserializer(Class 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()) { @@ -567,6 +597,15 @@ public LongDeserializer(Class 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()) { @@ -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 { diff --git a/src/main/java/tools/jackson/databind/deser/std/FromStringWithRadixToNumberDeserializer.java b/src/main/java/tools/jackson/databind/deser/std/FromStringWithRadixToNumberDeserializer.java new file mode 100644 index 0000000000..2dae3dd1c8 --- /dev/null +++ b/src/main/java/tools/jackson/databind/deser/std/FromStringWithRadixToNumberDeserializer.java @@ -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 { + 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 + } + } + + +} \ No newline at end of file diff --git a/src/main/java/tools/jackson/databind/deser/std/RadixSerializerCreator.java b/src/main/java/tools/jackson/databind/deser/std/RadixSerializerCreator.java new file mode 100644 index 0000000000..13736233f4 --- /dev/null +++ b/src/main/java/tools/jackson/databind/deser/std/RadixSerializerCreator.java @@ -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 createRadixStringDeserializer( + StdScalarDeserializer 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(); + } +} diff --git a/src/main/java/tools/jackson/databind/introspect/ConcreteBeanPropertyBase.java b/src/main/java/tools/jackson/databind/introspect/ConcreteBeanPropertyBase.java index 44abaf8970..dd81a8be10 100644 --- a/src/main/java/tools/jackson/databind/introspect/ConcreteBeanPropertyBase.java +++ b/src/main/java/tools/jackson/databind/introspect/ConcreteBeanPropertyBase.java @@ -58,12 +58,18 @@ public JsonFormat.Value findFormatOverrides(MapperConfig config) { @Override public JsonFormat.Value findPropertyFormat(MapperConfig config, Class baseType) { + 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 diff --git a/src/main/java/tools/jackson/databind/ser/jdk/NumberSerializer.java b/src/main/java/tools/jackson/databind/ser/jdk/NumberSerializer.java index b3b75a0ce5..fa94721d85 100644 --- a/src/main/java/tools/jackson/databind/ser/jdk/NumberSerializer.java +++ b/src/main/java/tools/jackson/databind/ser/jdk/NumberSerializer.java @@ -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 @@ -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: } } @@ -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 */ diff --git a/src/main/java/tools/jackson/databind/ser/jdk/NumberSerializers.java b/src/main/java/tools/jackson/databind/ser/jdk/NumberSerializers.java index 541d2e4269..070b8da893 100644 --- a/src/main/java/tools/jackson/databind/ser/jdk/NumberSerializers.java +++ b/src/main/java/tools/jackson/databind/ser/jdk/NumberSerializers.java @@ -94,7 +94,7 @@ public ValueSerializer createContextual(SerializationContext 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/tools/jackson/databind/ser/jdk/NumberToStringWithRadixSerializer.java b/src/main/java/tools/jackson/databind/ser/jdk/NumberToStringWithRadixSerializer.java new file mode 100644 index 0000000000..1d1f1ad797 --- /dev/null +++ b/src/main/java/tools/jackson/databind/ser/jdk/NumberToStringWithRadixSerializer.java @@ -0,0 +1,68 @@ +package tools.jackson.databind.ser.jdk; + +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.annotation.JacksonStdImpl; +import tools.jackson.databind.ser.std.ToStringSerializerBase; + +import java.math.BigInteger; + +/** + * Serializer used to convert numbers into a representation for a specified radix (base) and serialize + * the representation as string. + * + * @since 3.1 + */ +@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(SerializationContext ctxt, Object value) { + return false; + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializationContext provider) + throws JacksonException + { + 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(); + } +} \ No newline at end of file diff --git a/src/test/java/tools/jackson/databind/format/DifferentRadixNumberFormatTest.java b/src/test/java/tools/jackson/databind/format/DifferentRadixNumberFormatTest.java new file mode 100644 index 0000000000..651c115554 --- /dev/null +++ b/src/test/java/tools/jackson/databind/format/DifferentRadixNumberFormatTest.java @@ -0,0 +1,218 @@ +package tools.jackson.databind.format; + +import com.fasterxml.jackson.annotation.JsonFormat; +import org.junit.jupiter.api.Test; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.testutil.DatabindTestUtil; + +import java.math.BigInteger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class DifferentRadixNumberFormatTest extends DatabindTestUtil { + + private static final int HEX_RADIX = 16; + private static final int 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, radix = 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, radix = BINARY_RADIX) + public byte byteValue; + @JsonFormat(shape = JsonFormat.Shape.STRING, radix = BINARY_RADIX) + public Byte ByteValue; + + @JsonFormat(shape = JsonFormat.Shape.STRING, radix = BINARY_RADIX) + public short shortValue; + @JsonFormat(shape = JsonFormat.Shape.STRING, radix = BINARY_RADIX) + public Short ShortValue; + + @JsonFormat(shape = JsonFormat.Shape.STRING, radix = BINARY_RADIX) + public int intValue; + @JsonFormat(shape = JsonFormat.Shape.STRING, radix = BINARY_RADIX) + public Integer IntegerValue; + + @JsonFormat(shape = JsonFormat.Shape.STRING, radix = BINARY_RADIX) + public long longValue; + @JsonFormat(shape = JsonFormat.Shape.STRING, radix = BINARY_RADIX) + public Long LongValue; + + @JsonFormat(shape = JsonFormat.Shape.STRING, radix = 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 testIntSerializedAsHexString() + { + ObjectMapper mapper = jsonMapperBuilder() + .withConfigOverride(int.class, + o -> o.setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING).withRadix(HEX_RADIX))) + .build(); + 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 testIntSerializedAsHexStringWithDefaultRadix() + { + ObjectMapper mapper = jsonMapperBuilder() + .defaultFormat(JsonFormat.Value.forRadix(HEX_RADIX).withShape(JsonFormat.Shape.STRING)) + .build(); + 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() + { + 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 testAnnotatedAccessorWithoutRadixDoesNotThrow() + { + ObjectMapper mapper = newJsonMapper(); + IncorrectlyAnnotatedMethodIntWrapper initialIntWrapper = new IncorrectlyAnnotatedMethodIntWrapper(10); + String expectedJson = "{'value':'10'}"; + + String json = mapper.writeValueAsString(initialIntWrapper); + + assertEquals(a2q(expectedJson), json); + } + + @Test + void testUsingDefaultConfigOverrideRadixToSerializeAsHexString() + { + ObjectMapper mapper = jsonMapperBuilder() + .withConfigOverride(Integer.class, + o -> o.setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING).withRadix(HEX_RADIX))) + .build(); + 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() + { + 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); + } +} \ No newline at end of file