Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NumberFormat support #4273

Open
wants to merge 4 commits into
base: 2.17
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -38,13 +41,26 @@ public class NumberSerializer

protected final boolean _isInt;

/**
* @since 2.17
*/
protected final NumberFormat format;
hurelhuyag marked this conversation as resolved.
Show resolved Hide resolved

/**
* @since 2.5
*/
public NumberSerializer(Class<? extends Number> rawType) {
this(rawType, null);
}

/**
* @since 2.17
*/
public NumberSerializer(Class<? extends Number> 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
Expand All @@ -55,6 +71,18 @@ public JsonSerializer<?> createContextual(SerializerProvider prov,
if (format != null) {
switch (format.getShape()) {
case STRING:
if (format.hasPattern()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (format.hasPattern()) {
// [databind#1114] since 2.17 : Support @JsonFormat for String, numbers, using String.format()
if (format.hasPattern()) {

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) {
return bigDecimalAsStringSerializer();
Expand All @@ -69,6 +97,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));
hurelhuyag marked this conversation as resolved.
Show resolved Hide resolved
return;
}
// should mostly come in as one of these two:
if (value instanceof BigDecimal) {
g.writeNumber((BigDecimal) value);
Expand Down
Original file line number Diff line number Diff line change
@@ -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, () -> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you check the exception message too (so it's the expected exception)

mapper.writeValueAsString(new InvalidPatternWrapper(BigDecimal.ZERO));
});
}
Comment on lines +24 to +52
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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));
});
}
protected static class InvalidPatternWrapper {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "#,##0.#.#")
public BigDecimal value;
public InvalidPatternWrapper(BigDecimal value) {
this.value = value;
}
}
@Test
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);*/
}
@Test
public void testInvalidPattern() throws Exception {
ObjectMapper mapper = newJsonMapper();
try {
mapper.writeValueAsString(new InvalidPatternWrapper(BigDecimal.ZERO));
fail();
} catch (JsonMappingException e) {
verifyException(e, "expected message part 1/2"); //
verifyException(e, "expected message part 2/2");
}
}

Couple of suggestion on test if that's okay with you 🙂

  1. Let's use JUnit 5, (meaning remove extends BaseMapTest
  2. Class declaration comes first then test methods (Check suggestion)
  3. As suggested above, check message by using try-catch and verifyException (from DatabindTestUtil) combo

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above comment seems like Jackson Coding Style convention idea 🤔... WDYT, @cowtowncoder ?

}