diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java index df3a3b4e54..232add68ef 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java @@ -4,6 +4,9 @@ import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; import com.fasterxml.jackson.annotation.JsonFormat; @@ -38,6 +41,11 @@ public class NumberSerializer protected final boolean _isInt; + /** + * @since 2.17 + */ + protected final NumberFormat _format; + /** * @since 2.5 */ @@ -45,6 +53,16 @@ public NumberSerializer(Class rawType) { super(rawType, false); // since this will NOT be constructed for Integer or Long, only case is: _isInt = (rawType == BigInteger.class); + _format = null; + } + + /** + * @since 2.17 + */ + public NumberSerializer(NumberSerializer src, NumberFormat format) { + super(src); + _isInt = src._isInt; + _format = format; } @Override @@ -55,6 +73,21 @@ public JsonSerializer createContextual(SerializerProvider prov, if (format != null) { switch (format.getShape()) { case STRING: + if (format.hasPattern()) { + DecimalFormat decimalFormat; + try { + if (format.hasLocale()) { + decimalFormat = new DecimalFormat(format.getPattern(), + DecimalFormatSymbols.getInstance(format.getLocale())); + } else { + decimalFormat = new DecimalFormat(format.getPattern()); + } + } catch (IllegalArgumentException e) { + return prov.reportBadDefinition(handledType(), + String.format("Invalid `DecimalFormat`: \"%s\"", format.getPattern())); + } + return new NumberSerializer(this, decimalFormat); + } // [databind#2264]: Need special handling for `BigDecimal` if (((Class) handledType()) == BigDecimal.class) { return bigDecimalAsStringSerializer(); @@ -69,6 +102,10 @@ public JsonSerializer createContextual(SerializerProvider prov, @Override public void serialize(Number value, JsonGenerator g, SerializerProvider provider) throws IOException { + if (_format != null) { + g.writeString(_format.format(value)); + return; + } // should mostly come in as one of these two: if (value instanceof BigDecimal) { g.writeNumber((BigDecimal) value); diff --git a/src/test/java/com/fasterxml/jackson/databind/format/NumberFormatTest.java b/src/test/java/com/fasterxml/jackson/databind/format/NumberFormatTest.java new file mode 100644 index 0000000000..04d581a7c1 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/format/NumberFormatTest.java @@ -0,0 +1,53 @@ +package com.fasterxml.jackson.databind.format; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Assert; + +import java.math.BigDecimal; + +import java.util.Locale; +import java.util.TimeZone; + +public class NumberFormatTest extends BaseMapTest +{ + protected static class NumberWrapper { + + public BigDecimal value; + + public NumberWrapper() {} + public NumberWrapper(BigDecimal v) { value = v; } + } + + public void testTypeDefaults() throws Exception + { + ObjectMapper mapper = newJsonMapper(); + mapper.configOverride(BigDecimal.class) + .setFormat(new JsonFormat.Value("00,000.00", JsonFormat.Shape.STRING, (Locale) null, (TimeZone) null, null, null)); + String json = mapper.writeValueAsString(new NumberWrapper(new BigDecimal("1234"))); + assertEquals(a2q("{'value':'01,234.00'}"), json); + + // and then read back is not supported yet. + /*NumberWrapper w = mapper.readValue(a2q("{'value':'01,234.00'}"), NumberWrapper.class); + assertNotNull(w); + assertEquals(new BigDecimal("1234"), w.value);*/ + } + + protected static class InvalidPatternWrapper { + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "#,##0.#.#") + public BigDecimal value; + + public InvalidPatternWrapper(BigDecimal value) { + this.value = value; + } + } + + public void testInvalidPattern() throws Exception { + ObjectMapper mapper = newJsonMapper(); + Assert.assertThrows(JsonMappingException.class, () -> { + mapper.writeValueAsString(new InvalidPatternWrapper(BigDecimal.ZERO)); + }); + } +}