Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,11 @@ public DeserializationConfig withRootName(PropertyName rootName) {
return new DeserializationConfig(this, rootName);
}

@Override
public DeserializationConfig withRadix(int radix) {
return _withBase(_base.withRadix(radix));
}

@Override
public DeserializationConfig withView(Class<?> view) {
return (_view == view) ? this : new DeserializationConfig(this, view);
Expand Down
18 changes: 17 additions & 1 deletion src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ public boolean useForType(JavaType t)
// 16-May-2009, tatu: Ditto ^^^
protected final static AnnotationIntrospector DEFAULT_ANNOTATION_INTROSPECTOR = new JacksonAnnotationIntrospector();

protected static final int DEFAULT_RADIX = 10;
/**
* Base settings contain defaults used for all {@link ObjectMapper}
* instances.
Expand All @@ -419,7 +420,9 @@ public boolean useForType(JavaType t)
// Since 2.12:
new DefaultAccessorNamingStrategy.Provider(),
// Since 2.16: [databind#2502] Add a way to configure Caches Jackson uses
DefaultCacheProvider.defaultInstance()
DefaultCacheProvider.defaultInstance(),
//since 2.21: [databind#221] - support alternate radixes for numerical values serialized as strings
DEFAULT_RADIX
);

/*
Expand Down Expand Up @@ -2545,6 +2548,19 @@ public ObjectMapper setDateFormat(DateFormat dateFormat)
return this;
}

/**
* Method for configuring the radix to use when serializing integral types
* as strings.
*
* @since 2.21
*/
public ObjectMapper setRadix(int radix)
{
_deserializationConfig = _deserializationConfig.withRadix(radix);
_serializationConfig = _serializationConfig.withRadix(radix);
return this;
}

/**
* @since 2.5
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,11 @@ public SerializationConfig with(SubtypeResolver str) {
return (str == _subtypeResolver)? this : new SerializationConfig(this, str);
}

@Override
public SerializationConfig withRadix(int radix) {
return _withBase(_base.withRadix(radix));
}

@Override
public SerializationConfig withView(Class<?> view) {
return (_view == view) ? this : new SerializationConfig(this, view);
Expand Down
84 changes: 67 additions & 17 deletions src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public final class BaseSettings
*/
TimeZone.getTimeZone("UTC");

private static final int DEFAULT_RADIX = 10;

/*
/**********************************************************
/* Configuration settings; introspection, related
Expand Down Expand Up @@ -155,21 +157,28 @@ public final class BaseSettings
*/
protected final CacheProvider _cacheProvider;

/**
* Default radix to use when serializing/deserializing integers to string.
*
* @since 2.21
*/
protected final int _defaultRadix;

/*
/**********************************************************
/* Construction
/**********************************************************
*/

/**
* @since 2.19
* @since 2.21
*/
public BaseSettings(ClassIntrospector ci, AnnotationIntrospector ai,
PropertyNamingStrategy pns, EnumNamingStrategy ens, TypeFactory tf,
TypeResolverBuilder<?> typer, DateFormat dateFormat, HandlerInstantiator hi,
Locale locale, TimeZone tz, Base64Variant defaultBase64,
PolymorphicTypeValidator ptv, AccessorNamingStrategy.Provider accNaming,
CacheProvider cacheProvider)
CacheProvider cacheProvider, int defaultRadix)
{
_classIntrospector = ci;
_annotationIntrospector = ai;
Expand All @@ -185,6 +194,21 @@ public BaseSettings(ClassIntrospector ci, AnnotationIntrospector ai,
_typeValidator = ptv;
_accessorNaming = accNaming;
_cacheProvider = cacheProvider;
_defaultRadix = defaultRadix;
}

/**
* @since 2.19
* @deprecated since 2.21, use variant that takes defaultRadix
*/
public BaseSettings(ClassIntrospector ci, AnnotationIntrospector ai,
PropertyNamingStrategy pns, EnumNamingStrategy ens, TypeFactory tf,
TypeResolverBuilder<?> typer, DateFormat dateFormat, HandlerInstantiator hi,
Locale locale, TimeZone tz, Base64Variant defaultBase64,
PolymorphicTypeValidator ptv, AccessorNamingStrategy.Provider accNaming,
CacheProvider cacheProvider)
{
this(ci, ai, pns, ens, tf, typer, dateFormat, hi, locale, tz, defaultBase64, ptv, accNaming, cacheProvider, DEFAULT_RADIX);
}

/**
Expand Down Expand Up @@ -237,7 +261,8 @@ public BaseSettings copy() {
_defaultBase64,
_typeValidator,
_accessorNaming,
_cacheProvider);
_cacheProvider,
_defaultRadix);
}

/*
Expand All @@ -252,7 +277,7 @@ public BaseSettings withClassIntrospector(ClassIntrospector ci) {
}
return new BaseSettings(ci, _annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy,
_typeFactory, _typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider);
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider, _defaultRadix);
}

public BaseSettings withAnnotationIntrospector(AnnotationIntrospector ai) {
Expand All @@ -261,7 +286,7 @@ public BaseSettings withAnnotationIntrospector(AnnotationIntrospector ai) {
}
return new BaseSettings(_classIntrospector, ai, _propertyNamingStrategy, _enumNamingStrategy,
_typeFactory, _typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider);
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider, _defaultRadix);
}

public BaseSettings withInsertedAnnotationIntrospector(AnnotationIntrospector ai) {
Expand All @@ -288,7 +313,7 @@ public BaseSettings withPropertyNamingStrategy(PropertyNamingStrategy pns) {
}
return new BaseSettings(_classIntrospector, _annotationIntrospector, pns, _enumNamingStrategy,
_typeFactory, _typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider);
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider, _defaultRadix);
}

/**
Expand All @@ -300,7 +325,7 @@ public BaseSettings withEnumNamingStrategy(EnumNamingStrategy ens) {
}
return new BaseSettings(_classIntrospector, _annotationIntrospector, _propertyNamingStrategy, ens,
_typeFactory, _typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider);
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider, _defaultRadix);
}

// @since 2.12
Expand All @@ -310,7 +335,7 @@ public BaseSettings withAccessorNaming(AccessorNamingStrategy.Provider p) {
}
return new BaseSettings(_classIntrospector, _annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy,
_typeFactory, _typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _typeValidator, p, _cacheProvider);
_timeZone, _defaultBase64, _typeValidator, p, _cacheProvider, _defaultRadix);
}

public BaseSettings withTypeFactory(TypeFactory tf) {
Expand All @@ -319,7 +344,7 @@ public BaseSettings withTypeFactory(TypeFactory tf) {
}
return new BaseSettings(_classIntrospector, _annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy,
tf, _typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider);
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider, _defaultRadix);
}

public BaseSettings withTypeResolverBuilder(TypeResolverBuilder<?> typer) {
Expand All @@ -328,7 +353,7 @@ public BaseSettings withTypeResolverBuilder(TypeResolverBuilder<?> typer) {
}
return new BaseSettings(_classIntrospector, _annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy,
_typeFactory, typer, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider);
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider, _defaultRadix);
}

public BaseSettings withDateFormat(DateFormat df) {
Expand All @@ -342,7 +367,7 @@ public BaseSettings withDateFormat(DateFormat df) {
}
return new BaseSettings(_classIntrospector, _annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy,
_typeFactory, _typeResolverBuilder, df, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider);
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider, _defaultRadix);
}

public BaseSettings withHandlerInstantiator(HandlerInstantiator hi) {
Expand All @@ -351,7 +376,7 @@ public BaseSettings withHandlerInstantiator(HandlerInstantiator hi) {
}
return new BaseSettings(_classIntrospector, _annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy,
_typeFactory, _typeResolverBuilder, _dateFormat, hi, _locale,
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider);
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider, _defaultRadix);
}

public BaseSettings with(Locale l) {
Expand All @@ -360,7 +385,7 @@ public BaseSettings with(Locale l) {
}
return new BaseSettings(_classIntrospector, _annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy,
_typeFactory,_typeResolverBuilder, _dateFormat, _handlerInstantiator, l,
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider);
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider, _defaultRadix);
}

/**
Expand All @@ -383,7 +408,7 @@ public BaseSettings with(TimeZone tz)
return new BaseSettings(_classIntrospector, _annotationIntrospector,
_propertyNamingStrategy, _enumNamingStrategy, _typeFactory,
_typeResolverBuilder, df, _handlerInstantiator, _locale,
tz, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider);
tz, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider, _defaultRadix);
}

/**
Expand All @@ -396,7 +421,7 @@ public BaseSettings with(Base64Variant base64) {
return new BaseSettings(_classIntrospector, _annotationIntrospector,
_propertyNamingStrategy, _enumNamingStrategy, _typeFactory,
_typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, base64, _typeValidator, _accessorNaming, _cacheProvider);
_timeZone, base64, _typeValidator, _accessorNaming, _cacheProvider, _defaultRadix);
}

/**
Expand All @@ -409,7 +434,7 @@ public BaseSettings with(PolymorphicTypeValidator v) {
return new BaseSettings(_classIntrospector, _annotationIntrospector,
_propertyNamingStrategy, _enumNamingStrategy, _typeFactory,
_typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, v, _accessorNaming, _cacheProvider);
_timeZone, _defaultBase64, v, _accessorNaming, _cacheProvider, _defaultRadix);
}

/**
Expand All @@ -425,7 +450,23 @@ public BaseSettings with(CacheProvider cacheProvider) {
return new BaseSettings(_classIntrospector, _annotationIntrospector,
_propertyNamingStrategy, _enumNamingStrategy, _typeFactory,
_typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, cacheProvider);
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, cacheProvider, _defaultRadix);
}

/**
* Fluent factory for constructing a new instance with provided default radix.
*
* @return a new instance with provided defaultRadix.
* @since 2.21
*/
public BaseSettings withRadix(int defaultRadix) {
if (defaultRadix == _defaultRadix) {
return this;
}
return new BaseSettings(_classIntrospector, _annotationIntrospector,
_propertyNamingStrategy, _enumNamingStrategy, _typeFactory,
_typeResolverBuilder, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _typeValidator, _accessorNaming, _cacheProvider, defaultRadix);
}

/*
Expand Down Expand Up @@ -503,6 +544,15 @@ public boolean hasExplicitTimeZone() {
public Base64Variant getBase64Variant() {
return _defaultBase64;
}

/**
* Method indicating base to use for serializing/deserializing an integral number as a string.
*
* @since 2.21
*/
public int getRadix() {
return _defaultRadix;
}

/**
* @since 2.16
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,15 @@ public Base64Variant getBase64Variant() {
return _base.getBase64Variant();
}

/**
* Method indicating base to use for serializing/deserializing an integral number as a string.
*
* @since 2.21
*/
public int getRadix() {
return _base.getRadix();
}

/**
* Method for accessing per-instance shared (baseline/default)
* attribute values; these are used as the basis for per-call
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,14 @@ public T withRootName(String rootName) {
*/
public abstract T with(SubtypeResolver str);

/**
* Method for constructing and returning a new instance with different
* radix to use.
*
* @since 2.21
*/
public abstract T withRadix(int radix);

/**
* Method for constructing and returning a new instance with different
* view to use.
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Number> {
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");
Copy link
Author

@tiger9800 tiger9800 Sep 16, 2025

Choose a reason for hiding this comment

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

I considered explicitly throwing here, but this seemed like a more common way to indicate an error.

return null;//should not reach here
}
}
}
Loading