Skip to content

Commit 4cffce0

Browse files
committed
Add radix property to JsonFormat (#320)
1 parent 2073b9d commit 4cffce0

File tree

2 files changed

+130
-19
lines changed

2 files changed

+130
-19
lines changed

src/main/java/com/fasterxml/jackson/annotation/JsonFormat.java

Lines changed: 118 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
* This is useful to prevent large numeric values from being rounded to their closest double
4747
* values when deserialized by JSON parsers (for instance <code>JSON.parse()</code> in web
4848
* browsers) that do not support numbers with more than 53 bits of precision.
49+
* When serializing {@link java.lang.Number} to a string, it is possible to specify radix,
50+
* the numeric base used to output the number in.
4951
* <p>
5052
* They can also be serialized to full objects if {@link Shape#OBJECT} is used.
5153
* Otherwise, the default behavior of serializing to a scalar number value will be preferred.
@@ -78,6 +80,12 @@
7880
*/
7981
public final static String DEFAULT_TIMEZONE = "##default";
8082

83+
/**
84+
* Value that indicates the default radix(numeric base) to use for outputting {@link java.lang.Number} properties
85+
* when {@link Shape#STRING} is specified.
86+
*/
87+
public final static byte DEFAULT_RADIX = 10;
88+
8189
/**
8290
* Datatype-specific additional piece of configuration that may be used
8391
* to further refine formatting aspects. This may, for example, determine
@@ -126,6 +134,16 @@
126134
*/
127135
public OptBoolean lenient() default OptBoolean.DEFAULT;
128136

137+
/**
138+
* Property that indicates the numeric base used to output {@link java.lang.Number} properties when {@link Shape#STRING}
139+
* is specified.
140+
* For example, if 2 is used, then the output will be a binary representation of a number as a string,
141+
* and with 16, the number will be outputted in the hexadecimal form.
142+
*
143+
* @since 2.21
144+
*/
145+
public byte radix() default DEFAULT_RADIX;
146+
129147
/**
130148
* Set of {@link JsonFormat.Feature}s to explicitly enable with respect
131149
* to handling of annotated property. This will have precedence over possible
@@ -518,21 +536,41 @@ public static class Value
518536
*/
519537
private final Features _features;
520538

539+
/**
540+
* @since 2.21
541+
*/
542+
private final byte _radix;
543+
521544
// lazily constructed when created from annotations
522545
private transient TimeZone _timezone;
523546

524547
public Value() {
525-
this("", Shape.ANY, "", "", Features.empty(), null);
548+
this("", Shape.ANY, "", "", Features.empty(), null, DEFAULT_RADIX);
526549
}
527550

528551
public Value(JsonFormat ann) {
529552
this(ann.pattern(), ann.shape(), ann.locale(), ann.timezone(),
530-
Features.construct(ann), ann.lenient().asBoolean());
553+
Features.construct(ann), ann.lenient().asBoolean(), ann.radix());
554+
}
555+
556+
/**
557+
* @since 2.21
558+
*/
559+
public Value(String p, Shape sh, String localeStr, String tzStr, Features f,
560+
Boolean lenient, byte radix)
561+
{
562+
this(p, sh,
563+
(localeStr == null || localeStr.length() == 0 || DEFAULT_LOCALE.equals(localeStr)) ?
564+
null : new Locale(localeStr),
565+
(tzStr == null || tzStr.length() == 0 || DEFAULT_TIMEZONE.equals(tzStr)) ?
566+
null : tzStr,
567+
null, f, lenient, radix);
531568
}
532569

533570
/**
534571
* @since 2.9
535572
*/
573+
@Deprecated //since 2.21
536574
public Value(String p, Shape sh, String localeStr, String tzStr, Features f,
537575
Boolean lenient)
538576
{
@@ -544,9 +582,26 @@ public Value(String p, Shape sh, String localeStr, String tzStr, Features f,
544582
null, f, lenient);
545583
}
546584

585+
/**
586+
* @since 2.21
587+
*/
588+
public Value(String p, Shape sh, Locale l, TimeZone tz, Features f,
589+
Boolean lenient, byte radix)
590+
{
591+
_pattern = (p == null) ? "" : p;
592+
_shape = (sh == null) ? Shape.ANY : sh;
593+
_locale = l;
594+
_timezone = tz;
595+
_timezoneStr = null;
596+
_features = (f == null) ? Features.empty() : f;
597+
_lenient = lenient;
598+
_radix = radix;
599+
}
600+
547601
/**
548602
* @since 2.9
549603
*/
604+
@Deprecated //since 2.21
550605
public Value(String p, Shape sh, Locale l, TimeZone tz, Features f,
551606
Boolean lenient)
552607
{
@@ -557,13 +612,14 @@ public Value(String p, Shape sh, Locale l, TimeZone tz, Features f,
557612
_timezoneStr = null;
558613
_features = (f == null) ? Features.empty() : f;
559614
_lenient = lenient;
615+
_radix = DEFAULT_RADIX;
560616
}
561617

562618
/**
563-
* @since 2.9
619+
* @since 2.21
564620
*/
565621
public Value(String p, Shape sh, Locale l, String tzStr, TimeZone tz, Features f,
566-
Boolean lenient)
622+
Boolean lenient, byte radix)
567623
{
568624
_pattern = (p == null) ? "" : p;
569625
_shape = (sh == null) ? Shape.ANY : sh;
@@ -572,6 +628,17 @@ public Value(String p, Shape sh, Locale l, String tzStr, TimeZone tz, Features f
572628
_timezoneStr = tzStr;
573629
_features = (f == null) ? Features.empty() : f;
574630
_lenient = lenient;
631+
_radix = radix;
632+
}
633+
634+
/**
635+
* @since 2.9
636+
*/
637+
@Deprecated //since 2.21
638+
public Value(String p, Shape sh, Locale l, String tzStr, TimeZone tz, Features f,
639+
Boolean lenient)
640+
{
641+
this(p, sh, l, tzStr, tz, f, lenient, DEFAULT_RADIX);
575642
}
576643

577644
/**
@@ -662,37 +729,45 @@ public final Value withOverrides(Value overrides) {
662729
} else {
663730
tz = overrides._timezone;
664731
}
665-
return new Value(p, sh, l, tzStr, tz, f, lenient);
732+
return new Value(p, sh, l, tzStr, tz, f, lenient, overrides._radix);
666733
}
667734

668735
/**
669736
* @since 2.6
670737
*/
671738
public static Value forPattern(String p) {
672-
return new Value(p, null, null, null, null, Features.empty(), null);
739+
return new Value(p, null, null, null, null, Features.empty(), null, DEFAULT_RADIX);
673740
}
674741

675742
/**
676743
* @since 2.7
677744
*/
678745
public static Value forShape(Shape sh) {
679-
return new Value("", sh, null, null, null, Features.empty(), null);
746+
return new Value("", sh, null, null, null, Features.empty(), null, DEFAULT_RADIX);
680747
}
681748

682749
/**
683750
* @since 2.9
684751
*/
685752
public static Value forLeniency(boolean lenient) {
686753
return new Value("", null, null, null, null, Features.empty(),
687-
Boolean.valueOf(lenient));
754+
Boolean.valueOf(lenient), DEFAULT_RADIX);
755+
}
756+
757+
/**
758+
* @since 2.21
759+
*/
760+
public static Value forRadix(byte radix) {
761+
return new Value("", null, null, null, null, Features.empty(),
762+
null, radix);
688763
}
689764

690765
/**
691766
* @since 2.1
692767
*/
693768
public Value withPattern(String p) {
694769
return new Value(p, _shape, _locale, _timezoneStr, _timezone,
695-
_features, _lenient);
770+
_features, _lenient, _radix);
696771
}
697772

698773
/**
@@ -703,15 +778,15 @@ public Value withShape(Shape s) {
703778
return this;
704779
}
705780
return new Value(_pattern, s, _locale, _timezoneStr, _timezone,
706-
_features, _lenient);
781+
_features, _lenient, _radix);
707782
}
708783

709784
/**
710785
* @since 2.1
711786
*/
712787
public Value withLocale(Locale l) {
713788
return new Value(_pattern, _shape, l, _timezoneStr, _timezone,
714-
_features, _lenient);
789+
_features, _lenient, _radix);
715790
}
716791

717792
/**
@@ -730,7 +805,18 @@ public Value withLenient(Boolean lenient) {
730805
return this;
731806
}
732807
return new Value(_pattern, _shape, _locale, _timezoneStr, _timezone,
733-
_features, lenient);
808+
_features, lenient, _radix);
809+
}
810+
811+
/**
812+
* @since 2.21
813+
*/
814+
public Value withRadix(byte radix) {
815+
if (radix == _radix) {
816+
return this;
817+
}
818+
return new Value(_pattern, _shape, _locale, _timezoneStr, _timezone,
819+
_features, _lenient, radix);
734820
}
735821

736822
/**
@@ -740,7 +826,7 @@ public Value withFeature(JsonFormat.Feature f) {
740826
Features newFeats = _features.with(f);
741827
return (newFeats == _features) ? this :
742828
new Value(_pattern, _shape, _locale, _timezoneStr, _timezone,
743-
newFeats, _lenient);
829+
newFeats, _lenient, _radix);
744830
}
745831

746832
/**
@@ -750,7 +836,7 @@ public Value withoutFeature(JsonFormat.Feature f) {
750836
Features newFeats = _features.without(f);
751837
return (newFeats == _features) ? this :
752838
new Value(_pattern, _shape, _locale, _timezoneStr, _timezone,
753-
newFeats, _lenient);
839+
newFeats, _lenient, _radix);
754840
}
755841

756842
@Override
@@ -773,6 +859,11 @@ public Boolean getLenient() {
773859
return _lenient;
774860
}
775861

862+
/**
863+
* @since 2.21
864+
*/
865+
public byte getRadix() { return _radix; }
866+
776867
/**
777868
* Convenience method equivalent to
778869
*<pre>
@@ -848,6 +939,15 @@ public boolean hasLenient() {
848939
return _lenient != null;
849940
}
850941

942+
/**
943+
* Accessor for checking whether non-default (non-10) radix has been specified.
944+
*
945+
* @since 2.21
946+
*/
947+
public boolean hasNonDefaultRadix() {
948+
return _radix != DEFAULT_RADIX;
949+
}
950+
851951
/**
852952
* Accessor for checking whether this format value has specific setting for
853953
* given feature. Result is 3-valued with either `null`, {@link Boolean#TRUE} or
@@ -872,8 +972,8 @@ public Features getFeatures() {
872972

873973
@Override
874974
public String toString() {
875-
return String.format("JsonFormat.Value(pattern=%s,shape=%s,lenient=%s,locale=%s,timezone=%s,features=%s)",
876-
_pattern, _shape, _lenient, _locale, _timezoneStr, _features);
975+
return String.format("JsonFormat.Value(pattern=%s,shape=%s,lenient=%s,locale=%s,timezone=%s,features=%s,radix=%s)",
976+
_pattern, _shape, _lenient, _locale, _timezoneStr, _features, _radix);
877977
}
878978

879979
@Override
@@ -908,7 +1008,8 @@ public boolean equals(Object o) {
9081008
&& Objects.equals(_timezoneStr, other._timezoneStr)
9091009
&& Objects.equals(_pattern, other._pattern)
9101010
&& Objects.equals(_timezone, other._timezone)
911-
&& Objects.equals(_locale, other._locale);
1011+
&& Objects.equals(_locale, other._locale)
1012+
&& Objects.equals(_radix, other._radix);
9121013
}
9131014
}
9141015
}

src/test/java/com/fasterxml/jackson/annotation/JsonFormatTest.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import org.junit.jupiter.api.Test;
77

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

1011
/**
@@ -30,6 +31,7 @@ public void testEmptyInstanceDefaults() {
3031
assertFalse(empty.hasShape());
3132
assertFalse(empty.hasTimeZone());
3233
assertFalse(empty.hasLenient());
34+
assertFalse(empty.hasNonDefaultRadix());
3335

3436
assertFalse(empty.isLenient());
3537
}
@@ -63,9 +65,9 @@ public void testEquality() {
6365

6466
@Test
6567
public void testToString() {
66-
assertEquals("JsonFormat.Value(pattern=,shape=STRING,lenient=null,locale=null,timezone=null,features=EMPTY)",
68+
assertEquals("JsonFormat.Value(pattern=,shape=STRING,lenient=null,locale=null,timezone=null,features=EMPTY,radix=10)",
6769
JsonFormat.Value.forShape(JsonFormat.Shape.STRING).toString());
68-
assertEquals("JsonFormat.Value(pattern=[.],shape=ANY,lenient=null,locale=null,timezone=null,features=EMPTY)",
70+
assertEquals("JsonFormat.Value(pattern=[.],shape=ANY,lenient=null,locale=null,timezone=null,features=EMPTY,radix=10)",
6971
JsonFormat.Value.forPattern("[.]").toString());
7072
}
7173

@@ -146,6 +148,14 @@ public void testSimpleMerge()
146148
assertFalse(merged.hasLocale());
147149
assertEquals(TEST_SHAPE, merged.getShape());
148150
assertFalse(merged.hasTimeZone());
151+
152+
//radix always overrides
153+
byte binaryRadix = 2;
154+
final JsonFormat.Value v3 = JsonFormat.Value.forRadix(binaryRadix);
155+
merged = EMPTY.withOverrides(v3);
156+
assertEquals(DEFAULT_RADIX, EMPTY.getRadix());
157+
assertEquals(binaryRadix, merged.getRadix());
158+
149159
}
150160

151161
@Test

0 commit comments

Comments
 (0)