Skip to content

Commit 437705a

Browse files
authored
Add radix property to JsonFormat (#320) (#321)
1 parent 2073b9d commit 437705a

File tree

2 files changed

+151
-19
lines changed

2 files changed

+151
-19
lines changed

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

Lines changed: 125 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,13 @@
7880
*/
7981
public final static String DEFAULT_TIMEZONE = "##default";
8082

83+
/**
84+
* This is a marker signaling that a configured default radix should be used, which typically means 10,
85+
* when serializing {@link java.lang.Number} properties with {@link Shape#STRING}.
86+
* @since 2.21
87+
*/
88+
public final static int DEFAULT_RADIX = -1;
89+
8190
/**
8291
* Datatype-specific additional piece of configuration that may be used
8392
* to further refine formatting aspects. This may, for example, determine
@@ -126,6 +135,16 @@
126135
*/
127136
public OptBoolean lenient() default OptBoolean.DEFAULT;
128137

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

540+
/**
541+
* @since 2.21
542+
*/
543+
private final int _radix;
544+
521545
// lazily constructed when created from annotations
522546
private transient TimeZone _timezone;
523547

524548
public Value() {
525-
this("", Shape.ANY, "", "", Features.empty(), null);
549+
this("", Shape.ANY, "", "", Features.empty(), null, DEFAULT_RADIX);
526550
}
527551

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

533571
/**
534572
* @since 2.9
535573
*/
574+
@Deprecated //since 2.21
536575
public Value(String p, Shape sh, String localeStr, String tzStr, Features f,
537576
Boolean lenient)
538577
{
@@ -544,9 +583,26 @@ public Value(String p, Shape sh, String localeStr, String tzStr, Features f,
544583
null, f, lenient);
545584
}
546585

586+
/**
587+
* @since 2.21
588+
*/
589+
public Value(String p, Shape sh, Locale l, TimeZone tz, Features f,
590+
Boolean lenient, int radix)
591+
{
592+
_pattern = (p == null) ? "" : p;
593+
_shape = (sh == null) ? Shape.ANY : sh;
594+
_locale = l;
595+
_timezone = tz;
596+
_timezoneStr = null;
597+
_features = (f == null) ? Features.empty() : f;
598+
_lenient = lenient;
599+
_radix = radix;
600+
}
601+
547602
/**
548603
* @since 2.9
549604
*/
605+
@Deprecated //since 2.21
550606
public Value(String p, Shape sh, Locale l, TimeZone tz, Features f,
551607
Boolean lenient)
552608
{
@@ -557,13 +613,14 @@ public Value(String p, Shape sh, Locale l, TimeZone tz, Features f,
557613
_timezoneStr = null;
558614
_features = (f == null) ? Features.empty() : f;
559615
_lenient = lenient;
616+
_radix = DEFAULT_RADIX;
560617
}
561618

562619
/**
563-
* @since 2.9
620+
* @since 2.21
564621
*/
565622
public Value(String p, Shape sh, Locale l, String tzStr, TimeZone tz, Features f,
566-
Boolean lenient)
623+
Boolean lenient, int radix)
567624
{
568625
_pattern = (p == null) ? "" : p;
569626
_shape = (sh == null) ? Shape.ANY : sh;
@@ -572,6 +629,17 @@ public Value(String p, Shape sh, Locale l, String tzStr, TimeZone tz, Features f
572629
_timezoneStr = tzStr;
573630
_features = (f == null) ? Features.empty() : f;
574631
_lenient = lenient;
632+
_radix = radix;
633+
}
634+
635+
/**
636+
* @since 2.9
637+
*/
638+
@Deprecated //since 2.21
639+
public Value(String p, Shape sh, Locale l, String tzStr, TimeZone tz, Features f,
640+
Boolean lenient)
641+
{
642+
this(p, sh, l, tzStr, tz, f, lenient, DEFAULT_RADIX);
575643
}
576644

577645
/**
@@ -651,6 +719,10 @@ public final Value withOverrides(Value overrides) {
651719
if (lenient == null) {
652720
lenient = _lenient;
653721
}
722+
int radix = overrides._radix;
723+
if(radix == DEFAULT_RADIX) {
724+
radix = _radix;
725+
}
654726

655727
// timezone not merged, just choose one
656728
String tzStr = overrides._timezoneStr;
@@ -662,37 +734,45 @@ public final Value withOverrides(Value overrides) {
662734
} else {
663735
tz = overrides._timezone;
664736
}
665-
return new Value(p, sh, l, tzStr, tz, f, lenient);
737+
return new Value(p, sh, l, tzStr, tz, f, lenient, radix);
666738
}
667739

668740
/**
669741
* @since 2.6
670742
*/
671743
public static Value forPattern(String p) {
672-
return new Value(p, null, null, null, null, Features.empty(), null);
744+
return new Value(p, null, null, null, null, Features.empty(), null, DEFAULT_RADIX);
673745
}
674746

675747
/**
676748
* @since 2.7
677749
*/
678750
public static Value forShape(Shape sh) {
679-
return new Value("", sh, null, null, null, Features.empty(), null);
751+
return new Value("", sh, null, null, null, Features.empty(), null, DEFAULT_RADIX);
680752
}
681753

682754
/**
683755
* @since 2.9
684756
*/
685757
public static Value forLeniency(boolean lenient) {
686758
return new Value("", null, null, null, null, Features.empty(),
687-
Boolean.valueOf(lenient));
759+
Boolean.valueOf(lenient), DEFAULT_RADIX);
760+
}
761+
762+
/**
763+
* @since 2.21
764+
*/
765+
public static Value forRadix(int radix) {
766+
return new Value("", null, null, null, null, Features.empty(),
767+
null, radix);
688768
}
689769

690770
/**
691771
* @since 2.1
692772
*/
693773
public Value withPattern(String p) {
694774
return new Value(p, _shape, _locale, _timezoneStr, _timezone,
695-
_features, _lenient);
775+
_features, _lenient, _radix);
696776
}
697777

698778
/**
@@ -703,15 +783,15 @@ public Value withShape(Shape s) {
703783
return this;
704784
}
705785
return new Value(_pattern, s, _locale, _timezoneStr, _timezone,
706-
_features, _lenient);
786+
_features, _lenient, _radix);
707787
}
708788

709789
/**
710790
* @since 2.1
711791
*/
712792
public Value withLocale(Locale l) {
713793
return new Value(_pattern, _shape, l, _timezoneStr, _timezone,
714-
_features, _lenient);
794+
_features, _lenient, _radix);
715795
}
716796

717797
/**
@@ -730,7 +810,18 @@ public Value withLenient(Boolean lenient) {
730810
return this;
731811
}
732812
return new Value(_pattern, _shape, _locale, _timezoneStr, _timezone,
733-
_features, lenient);
813+
_features, lenient, _radix);
814+
}
815+
816+
/**
817+
* @since 2.21
818+
*/
819+
public Value withRadix(int radix) {
820+
if (radix == _radix) {
821+
return this;
822+
}
823+
return new Value(_pattern, _shape, _locale, _timezoneStr, _timezone,
824+
_features, _lenient, radix);
734825
}
735826

736827
/**
@@ -740,7 +831,7 @@ public Value withFeature(JsonFormat.Feature f) {
740831
Features newFeats = _features.with(f);
741832
return (newFeats == _features) ? this :
742833
new Value(_pattern, _shape, _locale, _timezoneStr, _timezone,
743-
newFeats, _lenient);
834+
newFeats, _lenient, _radix);
744835
}
745836

746837
/**
@@ -750,7 +841,7 @@ public Value withoutFeature(JsonFormat.Feature f) {
750841
Features newFeats = _features.without(f);
751842
return (newFeats == _features) ? this :
752843
new Value(_pattern, _shape, _locale, _timezoneStr, _timezone,
753-
newFeats, _lenient);
844+
newFeats, _lenient, _radix);
754845
}
755846

756847
@Override
@@ -773,6 +864,13 @@ public Boolean getLenient() {
773864
return _lenient;
774865
}
775866

867+
/**
868+
* @return radix to use for serializing subclasses of {@link Number} as strings.
869+
* If set to -1, a custom radix has not been specified.
870+
* @since 2.21
871+
*/
872+
public int getRadix() { return _radix; }
873+
776874
/**
777875
* Convenience method equivalent to
778876
*<pre>
@@ -848,6 +946,15 @@ public boolean hasLenient() {
848946
return _lenient != null;
849947
}
850948

949+
/**
950+
* Accessor for checking whether non-default radix has been specified.
951+
*
952+
* @since 2.21
953+
*/
954+
public boolean hasNonDefaultRadix() {
955+
return _radix != DEFAULT_RADIX;
956+
}
957+
851958
/**
852959
* Accessor for checking whether this format value has specific setting for
853960
* given feature. Result is 3-valued with either `null`, {@link Boolean#TRUE} or
@@ -872,8 +979,8 @@ public Features getFeatures() {
872979

873980
@Override
874981
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);
982+
return String.format("JsonFormat.Value(pattern=%s,shape=%s,lenient=%s,locale=%s,timezone=%s,features=%s,radix=%s)",
983+
_pattern, _shape, _lenient, _locale, _timezoneStr, _features, _radix);
877984
}
878985

879986
@Override
@@ -908,7 +1015,8 @@ public boolean equals(Object o) {
9081015
&& Objects.equals(_timezoneStr, other._timezoneStr)
9091016
&& Objects.equals(_pattern, other._pattern)
9101017
&& Objects.equals(_timezone, other._timezone)
911-
&& Objects.equals(_locale, other._locale);
1018+
&& Objects.equals(_locale, other._locale)
1019+
&& Objects.equals(_radix, other._radix);
9121020
}
9131021
}
9141022
}

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

Lines changed: 26 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=-1)",
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=-1)",
6971
JsonFormat.Value.forPattern("[.]").toString());
7072
}
7173

@@ -260,4 +262,26 @@ public void testFeatures() {
260262
assertEquals(Boolean.FALSE, f4.get(Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
261263
assertEquals(Boolean.TRUE, f4.get(Feature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS));
262264
}
265+
266+
@Test
267+
void testRadix() {
268+
//Non-Default radix overrides the default
269+
int binaryRadix = 2;
270+
final JsonFormat.Value v = JsonFormat.Value.forRadix(binaryRadix);
271+
JsonFormat.Value merged = EMPTY.withOverrides(v);
272+
assertEquals(DEFAULT_RADIX, EMPTY.getRadix());
273+
assertEquals(binaryRadix, merged.getRadix());
274+
275+
//Default does not override
276+
final JsonFormat.Value v2 = JsonFormat.Value.forRadix(binaryRadix);
277+
merged = v2.withOverrides(EMPTY);
278+
assertEquals(binaryRadix, v2.getRadix());
279+
assertEquals(binaryRadix, merged.getRadix());
280+
281+
JsonFormat.Value emptyWithBinaryRadix = EMPTY.withRadix(binaryRadix);
282+
assertEquals(binaryRadix, emptyWithBinaryRadix.getRadix());
283+
284+
JsonFormat.Value forBinaryRadix = JsonFormat.Value.forRadix(binaryRadix);
285+
assertEquals(binaryRadix, forBinaryRadix.getRadix());
286+
}
263287
}

0 commit comments

Comments
 (0)