Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
135 changes: 118 additions & 17 deletions src/main/java/com/fasterxml/jackson/annotation/JsonFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
* This is useful to prevent large numeric values from being rounded to their closest double
* values when deserialized by JSON parsers (for instance <code>JSON.parse()</code> in web
* browsers) that do not support numbers with more than 53 bits of precision.
* When serializing {@link java.lang.Number} to a string, it is possible to specify radix,
* the numeric base used to output the number in.
* <p>
* They can also be serialized to full objects if {@link Shape#OBJECT} is used.
* Otherwise, the default behavior of serializing to a scalar number value will be preferred.
Expand Down Expand Up @@ -78,6 +80,12 @@
*/
public final static String DEFAULT_TIMEZONE = "##default";

/**
* Value that indicates the default radix(numeric base) to use for outputting {@link java.lang.Number} properties
* when {@link Shape#STRING} is specified.
*/
public final static byte DEFAULT_RADIX = 10;

/**
* Datatype-specific additional piece of configuration that may be used
* to further refine formatting aspects. This may, for example, determine
Expand Down Expand Up @@ -126,6 +134,16 @@
*/
public OptBoolean lenient() default OptBoolean.DEFAULT;

/**
* Property that indicates the numeric base used to output {@link java.lang.Number} properties when {@link Shape#STRING}
* is specified.
* For example, if 2 is used, then the output will be a binary representation of a number as a string,
* and with 16, the number will be outputted in the hexadecimal form.
*
* @since 2.21
*/
public byte radix() default DEFAULT_RADIX;

/**
* Set of {@link JsonFormat.Feature}s to explicitly enable with respect
* to handling of annotated property. This will have precedence over possible
Expand Down Expand Up @@ -518,21 +536,41 @@ public static class Value
*/
private final Features _features;

/**
* @since 2.21
*/
private final byte _radix;

// lazily constructed when created from annotations
private transient TimeZone _timezone;

public Value() {
this("", Shape.ANY, "", "", Features.empty(), null);
this("", Shape.ANY, "", "", Features.empty(), null, DEFAULT_RADIX);
}

public Value(JsonFormat ann) {
this(ann.pattern(), ann.shape(), ann.locale(), ann.timezone(),
Features.construct(ann), ann.lenient().asBoolean());
Features.construct(ann), ann.lenient().asBoolean(), ann.radix());
}

/**
* @since 2.21
*/
public Value(String p, Shape sh, String localeStr, String tzStr, Features f,
Boolean lenient, byte radix)
{
this(p, sh,
(localeStr == null || localeStr.length() == 0 || DEFAULT_LOCALE.equals(localeStr)) ?
null : new Locale(localeStr),
(tzStr == null || tzStr.length() == 0 || DEFAULT_TIMEZONE.equals(tzStr)) ?
null : tzStr,
null, f, lenient, radix);
}

/**
* @since 2.9
*/
@Deprecated //since 2.21
public Value(String p, Shape sh, String localeStr, String tzStr, Features f,
Boolean lenient)
{
Expand All @@ -544,9 +582,26 @@ public Value(String p, Shape sh, String localeStr, String tzStr, Features f,
null, f, lenient);
}

/**
* @since 2.21
*/
public Value(String p, Shape sh, Locale l, TimeZone tz, Features f,
Boolean lenient, byte radix)
{
_pattern = (p == null) ? "" : p;
_shape = (sh == null) ? Shape.ANY : sh;
_locale = l;
_timezone = tz;
_timezoneStr = null;
_features = (f == null) ? Features.empty() : f;
_lenient = lenient;
_radix = radix;
}

/**
* @since 2.9
*/
@Deprecated //since 2.21
public Value(String p, Shape sh, Locale l, TimeZone tz, Features f,
Boolean lenient)
{
Expand All @@ -557,13 +612,14 @@ public Value(String p, Shape sh, Locale l, TimeZone tz, Features f,
_timezoneStr = null;
_features = (f == null) ? Features.empty() : f;
_lenient = lenient;
_radix = DEFAULT_RADIX;
}

/**
* @since 2.9
* @since 2.21
*/
public Value(String p, Shape sh, Locale l, String tzStr, TimeZone tz, Features f,
Boolean lenient)
Boolean lenient, byte radix)
{
_pattern = (p == null) ? "" : p;
_shape = (sh == null) ? Shape.ANY : sh;
Expand All @@ -572,6 +628,17 @@ public Value(String p, Shape sh, Locale l, String tzStr, TimeZone tz, Features f
_timezoneStr = tzStr;
_features = (f == null) ? Features.empty() : f;
_lenient = lenient;
_radix = radix;
}

/**
* @since 2.9
*/
@Deprecated //since 2.21
public Value(String p, Shape sh, Locale l, String tzStr, TimeZone tz, Features f,
Boolean lenient)
{
this(p, sh, l, tzStr, tz, f, lenient, DEFAULT_RADIX);
}

/**
Expand Down Expand Up @@ -662,37 +729,45 @@ public final Value withOverrides(Value overrides) {
} else {
tz = overrides._timezone;
}
return new Value(p, sh, l, tzStr, tz, f, lenient);
return new Value(p, sh, l, tzStr, tz, f, lenient, overrides._radix);
}

/**
* @since 2.6
*/
public static Value forPattern(String p) {
return new Value(p, null, null, null, null, Features.empty(), null);
return new Value(p, null, null, null, null, Features.empty(), null, DEFAULT_RADIX);
}

/**
* @since 2.7
*/
public static Value forShape(Shape sh) {
return new Value("", sh, null, null, null, Features.empty(), null);
return new Value("", sh, null, null, null, Features.empty(), null, DEFAULT_RADIX);
}

/**
* @since 2.9
*/
public static Value forLeniency(boolean lenient) {
return new Value("", null, null, null, null, Features.empty(),
Boolean.valueOf(lenient));
Boolean.valueOf(lenient), DEFAULT_RADIX);
}

/**
* @since 2.21
*/
public static Value forRadix(byte radix) {
return new Value("", null, null, null, null, Features.empty(),
null, radix);
}

/**
* @since 2.1
*/
public Value withPattern(String p) {
return new Value(p, _shape, _locale, _timezoneStr, _timezone,
_features, _lenient);
_features, _lenient, _radix);
}

/**
Expand All @@ -703,15 +778,15 @@ public Value withShape(Shape s) {
return this;
}
return new Value(_pattern, s, _locale, _timezoneStr, _timezone,
_features, _lenient);
_features, _lenient, _radix);
}

/**
* @since 2.1
*/
public Value withLocale(Locale l) {
return new Value(_pattern, _shape, l, _timezoneStr, _timezone,
_features, _lenient);
_features, _lenient, _radix);
}

/**
Expand All @@ -730,7 +805,18 @@ public Value withLenient(Boolean lenient) {
return this;
}
return new Value(_pattern, _shape, _locale, _timezoneStr, _timezone,
_features, lenient);
_features, lenient, _radix);
}

/**
* @since 2.21
*/
public Value withRadix(byte radix) {
if (radix == _radix) {
return this;
}
return new Value(_pattern, _shape, _locale, _timezoneStr, _timezone,
_features, _lenient, radix);
}

/**
Expand All @@ -740,7 +826,7 @@ public Value withFeature(JsonFormat.Feature f) {
Features newFeats = _features.with(f);
return (newFeats == _features) ? this :
new Value(_pattern, _shape, _locale, _timezoneStr, _timezone,
newFeats, _lenient);
newFeats, _lenient, _radix);
}

/**
Expand All @@ -750,7 +836,7 @@ public Value withoutFeature(JsonFormat.Feature f) {
Features newFeats = _features.without(f);
return (newFeats == _features) ? this :
new Value(_pattern, _shape, _locale, _timezoneStr, _timezone,
newFeats, _lenient);
newFeats, _lenient, _radix);
}

@Override
Expand All @@ -773,6 +859,11 @@ public Boolean getLenient() {
return _lenient;
}

/**
* @since 2.21
*/
public byte getRadix() { return _radix; }

/**
* Convenience method equivalent to
*<pre>
Expand Down Expand Up @@ -848,6 +939,15 @@ public boolean hasLenient() {
return _lenient != null;
}

/**
* Accessor for checking whether non-default (non-10) radix has been specified.
*
* @since 2.21
*/
public boolean hasNonDefaultRadix() {
return _radix != DEFAULT_RADIX;
}

/**
* Accessor for checking whether this format value has specific setting for
* given feature. Result is 3-valued with either `null`, {@link Boolean#TRUE} or
Expand All @@ -872,8 +972,8 @@ public Features getFeatures() {

@Override
public String toString() {
return String.format("JsonFormat.Value(pattern=%s,shape=%s,lenient=%s,locale=%s,timezone=%s,features=%s)",
_pattern, _shape, _lenient, _locale, _timezoneStr, _features);
return String.format("JsonFormat.Value(pattern=%s,shape=%s,lenient=%s,locale=%s,timezone=%s,features=%s,radix=%s)",
_pattern, _shape, _lenient, _locale, _timezoneStr, _features, _radix);
}

@Override
Expand Down Expand Up @@ -908,7 +1008,8 @@ public boolean equals(Object o) {
&& Objects.equals(_timezoneStr, other._timezoneStr)
&& Objects.equals(_pattern, other._pattern)
&& Objects.equals(_timezone, other._timezone)
&& Objects.equals(_locale, other._locale);
&& Objects.equals(_locale, other._locale)
&& Objects.equals(_radix, other._radix);
}
}
}
14 changes: 12 additions & 2 deletions src/test/java/com/fasterxml/jackson/annotation/JsonFormatTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import org.junit.jupiter.api.Test;

import static com.fasterxml.jackson.annotation.JsonFormat.DEFAULT_RADIX;
import static org.junit.jupiter.api.Assertions.*;

/**
Expand All @@ -30,6 +31,7 @@ public void testEmptyInstanceDefaults() {
assertFalse(empty.hasShape());
assertFalse(empty.hasTimeZone());
assertFalse(empty.hasLenient());
assertFalse(empty.hasNonDefaultRadix());

assertFalse(empty.isLenient());
}
Expand Down Expand Up @@ -63,9 +65,9 @@ public void testEquality() {

@Test
public void testToString() {
assertEquals("JsonFormat.Value(pattern=,shape=STRING,lenient=null,locale=null,timezone=null,features=EMPTY)",
assertEquals("JsonFormat.Value(pattern=,shape=STRING,lenient=null,locale=null,timezone=null,features=EMPTY,radix=10)",
JsonFormat.Value.forShape(JsonFormat.Shape.STRING).toString());
assertEquals("JsonFormat.Value(pattern=[.],shape=ANY,lenient=null,locale=null,timezone=null,features=EMPTY)",
assertEquals("JsonFormat.Value(pattern=[.],shape=ANY,lenient=null,locale=null,timezone=null,features=EMPTY,radix=10)",
JsonFormat.Value.forPattern("[.]").toString());
}

Expand Down Expand Up @@ -146,6 +148,14 @@ public void testSimpleMerge()
assertFalse(merged.hasLocale());
assertEquals(TEST_SHAPE, merged.getShape());
assertFalse(merged.hasTimeZone());

//radix always overrides
byte binaryRadix = 2;
final JsonFormat.Value v3 = JsonFormat.Value.forRadix(binaryRadix);
merged = EMPTY.withOverrides(v3);
assertEquals(DEFAULT_RADIX, EMPTY.getRadix());
assertEquals(binaryRadix, merged.getRadix());

}

@Test
Expand Down