From adfc5c29e1037fae7cc1ccc3ad4513581f19cab8 Mon Sep 17 00:00:00 2001 From: Khurelkhuyag Date: Wed, 20 Dec 2023 22:58:20 +0800 Subject: [PATCH 1/4] NumberFormat support --- .../databind/ser/std/NumberSerializer.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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..2a525c8013 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,8 @@ import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; +import java.text.DecimalFormat; +import java.text.NumberFormat; import com.fasterxml.jackson.annotation.JsonFormat; @@ -38,13 +40,20 @@ public class NumberSerializer protected final boolean _isInt; + protected final NumberFormat format; + /** * @since 2.5 */ public NumberSerializer(Class rawType) { + this(rawType, null); + } + + public NumberSerializer(Class rawType, NumberFormat format) { super(rawType, false); // since this will NOT be constructed for Integer or Long, only case is: _isInt = (rawType == BigInteger.class); + this.format = format; } @Override @@ -55,6 +64,9 @@ public JsonSerializer createContextual(SerializerProvider prov, if (format != null) { switch (format.getShape()) { case STRING: + if (format.hasPattern()) { + return new NumberSerializer(handledType(), new DecimalFormat(format.getPattern())); + } // [databind#2264]: Need special handling for `BigDecimal` if (((Class) handledType()) == BigDecimal.class) { return bigDecimalAsStringSerializer(); @@ -69,6 +81,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); From 118555f92767a2c0c3170a52e881d9299118523b Mon Sep 17 00:00:00 2001 From: Khurelkhuyag Date: Thu, 21 Dec 2023 10:09:16 +0800 Subject: [PATCH 2/4] NumberFormat.format test --- .../databind/ser/std/NumberSerializer.java | 20 +++++++++-- .../databind/format/NumberFormatTest.java | 35 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/databind/format/NumberFormatTest.java 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 2a525c8013..e7b6c92ef1 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 @@ -5,6 +5,7 @@ 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; @@ -40,6 +41,9 @@ public class NumberSerializer protected final boolean _isInt; + /** + * @since 2.17 + */ protected final NumberFormat format; /** @@ -49,6 +53,9 @@ public NumberSerializer(Class rawType) { this(rawType, null); } + /** + * @since 2.17 + */ public NumberSerializer(Class rawType, NumberFormat format) { super(rawType, false); // since this will NOT be constructed for Integer or Long, only case is: @@ -65,7 +72,16 @@ public JsonSerializer createContextual(SerializerProvider prov, switch (format.getShape()) { case STRING: if (format.hasPattern()) { - return new NumberSerializer(handledType(), new DecimalFormat(format.getPattern())); + try { + if (format.hasLocale()) { + return new NumberSerializer(handledType(), new DecimalFormat(format.getPattern(), + DecimalFormatSymbols.getInstance(format.getLocale()))); + } + return new NumberSerializer(handledType(), new DecimalFormat(format.getPattern())); + } catch (IllegalArgumentException e) { + prov.reportMappingProblem(e, "Invalid DecimalFormat %s", + format.getPattern()); + } } // [databind#2264]: Need special handling for `BigDecimal` if (((Class) handledType()) == BigDecimal.class) { @@ -82,7 +98,7 @@ public JsonSerializer createContextual(SerializerProvider prov, public void serialize(Number value, JsonGenerator g, SerializerProvider provider) throws IOException { if (format != null) { - g.writeString(format.format(value)); + g.writeNumber(format.format(value)); return; } // should mostly come in as one of these two: 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..c9b77c3dab --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/format/NumberFormatTest.java @@ -0,0 +1,35 @@ +package com.fasterxml.jackson.databind.format; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; + +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);*/ + } +} From 3a50d67ce458cacb2aa12b1f3f279f3e8f430a69 Mon Sep 17 00:00:00 2001 From: Khurelkhuyag Date: Thu, 21 Dec 2023 10:57:53 +0800 Subject: [PATCH 3/4] invalid pattern test --- .../databind/ser/std/NumberSerializer.java | 2 +- .../databind/format/NumberFormatTest.java | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) 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 e7b6c92ef1..6576d4effa 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 @@ -98,7 +98,7 @@ public JsonSerializer createContextual(SerializerProvider prov, public void serialize(Number value, JsonGenerator g, SerializerProvider provider) throws IOException { if (format != null) { - g.writeNumber(format.format(value)); + g.writeString(format.format(value)); return; } // should mostly come in as one of these two: diff --git a/src/test/java/com/fasterxml/jackson/databind/format/NumberFormatTest.java b/src/test/java/com/fasterxml/jackson/databind/format/NumberFormatTest.java index c9b77c3dab..04d581a7c1 100644 --- a/src/test/java/com/fasterxml/jackson/databind/format/NumberFormatTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/format/NumberFormatTest.java @@ -2,7 +2,9 @@ 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; @@ -32,4 +34,20 @@ public void testTypeDefaults() throws Exception 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)); + }); + } } From 35f0c609f32fccff25c3815e6871e5a4080af94b Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Thu, 21 Dec 2023 21:23:19 -0800 Subject: [PATCH 4/4] Minor refactoring --- .../databind/ser/std/NumberSerializer.java | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) 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 6576d4effa..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 @@ -44,23 +44,25 @@ public class NumberSerializer /** * @since 2.17 */ - protected final NumberFormat format; + protected final NumberFormat _format; /** * @since 2.5 */ public NumberSerializer(Class rawType) { - this(rawType, null); + 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(Class rawType, NumberFormat format) { - super(rawType, false); - // since this will NOT be constructed for Integer or Long, only case is: - _isInt = (rawType == BigInteger.class); - this.format = format; + public NumberSerializer(NumberSerializer src, NumberFormat format) { + super(src); + _isInt = src._isInt; + _format = format; } @Override @@ -72,16 +74,19 @@ public JsonSerializer createContextual(SerializerProvider prov, switch (format.getShape()) { case STRING: if (format.hasPattern()) { + DecimalFormat decimalFormat; try { if (format.hasLocale()) { - return new NumberSerializer(handledType(), new DecimalFormat(format.getPattern(), - DecimalFormatSymbols.getInstance(format.getLocale()))); + decimalFormat = new DecimalFormat(format.getPattern(), + DecimalFormatSymbols.getInstance(format.getLocale())); + } else { + decimalFormat = new DecimalFormat(format.getPattern()); } - return new NumberSerializer(handledType(), new DecimalFormat(format.getPattern())); } catch (IllegalArgumentException e) { - prov.reportMappingProblem(e, "Invalid DecimalFormat %s", - format.getPattern()); + 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) { @@ -97,8 +102,8 @@ 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)); + if (_format != null) { + g.writeString(_format.format(value)); return; } // should mostly come in as one of these two: