diff --git a/joda-money/README.md b/joda-money/README.md index 8892c650..ec377411 100644 --- a/joda-money/README.md +++ b/joda-money/README.md @@ -44,3 +44,21 @@ Money amount = mapper.readValue("{\"currency\":\"EUR\",\"amount\":19.99}", Money assertEquals("EUR", amount.getCurrencyUnit().getCode()) assertEquals(BigDecimal.valueOf(19.99), amount.getAmount()) ``` + +### Configuring the module + +#### Amount representation + +Representation of the amount for the (de)serialized `Money` instances can be configured via the `JodaMoneyModule.withAmountRepresentation(AmountRepresentation)` method. The available representations are: + +* `DECIMAL_NUMBER` - the default; amounts are (de)serialized as decimal numbers equal to the monetary amount, e.g. `12.34` for EUR 12.34, +* `DECIMAL_STRING` - amounts are (de)serialized as strings containing decimal number equal to the monetary amount, e.g. `"12.34"` for EUR 12.34, +* `MINOR_CURRENCY_UNIT` - amounts are (de)serialized as long integers equal to the monetary amount expressed in minor currency unit, e.g. `1234` for EUR 12.34, `12345` for KWD 12.345 or `12` for JPY 12. + +Example usage: + +```java +ObjectMapper mapper = JsonMapper.builder() + .addModule(new JodaMoneyModule().withAmountRepresentation(AmountRepresentation.DECIMAL_STRING)) + .build(); +``` diff --git a/joda-money/pom.xml b/joda-money/pom.xml index 687915d5..abbc39b8 100644 --- a/joda-money/pom.xml +++ b/joda-money/pom.xml @@ -30,6 +30,12 @@ joda-money 1.0.1 + + pl.pragmatists + JUnitParams + 1.1.1 + test + diff --git a/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/AmountConverter.java b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/AmountConverter.java new file mode 100644 index 00000000..0c341f02 --- /dev/null +++ b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/AmountConverter.java @@ -0,0 +1,18 @@ +package com.fasterxml.jackson.datatype.jodamoney; + +import org.joda.money.CurrencyUnit; +import org.joda.money.Money; + +import java.math.BigDecimal; + +/** + * Common interface for amount conversion strategies used by {@link Money} (de)serializer. + * Allows conversion of {@code Money} to implementation-specific representation of its amount, + * and back to {@code Money}. + */ +interface AmountConverter { + + Object fromMoney(Money money); + + Money toMoney(CurrencyUnit currencyUnit, BigDecimal amount); +} diff --git a/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/AmountRepresentation.java b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/AmountRepresentation.java new file mode 100644 index 00000000..abd2ef84 --- /dev/null +++ b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/AmountRepresentation.java @@ -0,0 +1,36 @@ +package com.fasterxml.jackson.datatype.jodamoney; + +/** + * Enumeration of available strategies used by {@link MoneySerializer} and {@link MoneyDeserializer} + * to represent amounts of {@link org.joda.money.Money Money}. + */ +public enum AmountRepresentation { + + /** + * Decimal number representation, where amount is (de)serialized as decimal number equal + * to {@link org.joda.money.Money Money}'s amount, e.g. {@code 12.34} for + * {@code Money.parse("EUR 12.34")}. + * + * @see DecimalNumberAmountConverter + */ + DECIMAL_NUMBER, + + /** + * Decimal string representation, where amount is (de)serialized as string containing decimal + * number equal to {@link org.joda.money.Money Money}'s amount, e.g. {@code "12.34"} for + * {@code Money.parse("EUR 12.34")}. + * + * @see DecimalStringAmountConverter + */ + DECIMAL_STRING, + + /** + * Minor currency unit representation, where amount is (de)serialized as long integer equal + * to {@link org.joda.money.Money Money}'s amount expressed in minor currency unit, e.g. + * {@code 1234} for {@code Money.parse("EUR 12.34")}, {@code 12345} for + * {@code Money.parse("KWD 12.345")} or {@code 12} for {@code Money.parse("JPY 12")}. + * + * @see MinorCurrencyUnitAmountConverter + */ + MINOR_CURRENCY_UNIT, +} diff --git a/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/DecimalNumberAmountConverter.java b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/DecimalNumberAmountConverter.java new file mode 100644 index 00000000..7f4aa940 --- /dev/null +++ b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/DecimalNumberAmountConverter.java @@ -0,0 +1,37 @@ +package com.fasterxml.jackson.datatype.jodamoney; + +import org.joda.money.CurrencyUnit; +import org.joda.money.Money; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * An {@link AmountConverter} converting {@link Money} to its amount represented as + * {@link BigDecimal decimal number} (such as {@code 12.34} for {@code Money.parse("USD 12.34")}), + * and back to {@code Money} from this representation. + */ +final class DecimalNumberAmountConverter implements AmountConverter { + + private static final DecimalNumberAmountConverter INSTANCE = new DecimalNumberAmountConverter(); + + static DecimalNumberAmountConverter getInstance() { + return INSTANCE; + } + + private DecimalNumberAmountConverter() { + } + + @Override + public BigDecimal fromMoney(final Money money) { + final BigDecimal decimal = money.getAmount(); + final int decimalPlaces = money.getCurrencyUnit().getDecimalPlaces(); + final int scale = Math.max(decimal.scale(), decimalPlaces); + return decimal.setScale(scale, RoundingMode.UNNECESSARY); + } + + @Override + public Money toMoney(final CurrencyUnit currencyUnit, final BigDecimal amount) { + return Money.of(currencyUnit, amount); + } +} diff --git a/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/DecimalStringAmountConverter.java b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/DecimalStringAmountConverter.java new file mode 100644 index 00000000..5a5e4c90 --- /dev/null +++ b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/DecimalStringAmountConverter.java @@ -0,0 +1,33 @@ +package com.fasterxml.jackson.datatype.jodamoney; + +import org.joda.money.CurrencyUnit; +import org.joda.money.Money; + +import java.math.BigDecimal; + +/** + * An {@link AmountConverter} converting {@link Money} to its amount represented as decimal string + * (such as {@code "12.34"} for {@code Money.parse("USD 12.34")}), and back to {@code Money} from + * this representation. + */ +final class DecimalStringAmountConverter implements AmountConverter { + + private static final DecimalStringAmountConverter INSTANCE = new DecimalStringAmountConverter(); + + static DecimalStringAmountConverter getInstance() { + return INSTANCE; + } + + private DecimalStringAmountConverter() { + } + + @Override + public String fromMoney(final Money money) { + return DecimalNumberAmountConverter.getInstance().fromMoney(money).toPlainString(); + } + + @Override + public Money toMoney(final CurrencyUnit currencyUnit, final BigDecimal amount) { + return Money.of(currencyUnit, amount); + } +} diff --git a/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/JodaMoneyModule.java b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/JodaMoneyModule.java index f811bed8..5be884d3 100644 --- a/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/JodaMoneyModule.java +++ b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/JodaMoneyModule.java @@ -14,7 +14,15 @@ public class JodaMoneyModule extends Module { private static final long serialVersionUID = 1L; - public JodaMoneyModule() { } + private final AmountConverter amountConverter; + + private JodaMoneyModule(AmountConverter amountConverter) { + this.amountConverter = amountConverter; + } + + public JodaMoneyModule() { + this(DecimalNumberAmountConverter.getInstance()); + } @Override public String getModuleName() { @@ -34,12 +42,24 @@ public void setupModule(SetupContext context) { final SimpleDeserializers desers = new SimpleDeserializers(); desers.addDeserializer(CurrencyUnit.class, new CurrencyUnitDeserializer()); - desers.addDeserializer(Money.class, new MoneyDeserializer()); + desers.addDeserializer(Money.class, new MoneyDeserializer(amountConverter)); context.addDeserializers(desers); final SimpleSerializers sers = new SimpleSerializers(); sers.addSerializer(CurrencyUnit.class, new CurrencyUnitSerializer()); - sers.addSerializer(Money.class, new MoneySerializer()); + sers.addSerializer(Money.class, new MoneySerializer(amountConverter)); context.addSerializers(sers); } + + public JodaMoneyModule withAmountRepresentation(final AmountRepresentation representation) { + switch (representation) { + case DECIMAL_NUMBER: + return new JodaMoneyModule(DecimalNumberAmountConverter.getInstance()); + case DECIMAL_STRING: + return new JodaMoneyModule(DecimalStringAmountConverter.getInstance()); + case MINOR_CURRENCY_UNIT: + return new JodaMoneyModule(MinorCurrencyUnitAmountConverter.getInstance()); + } + throw new IllegalArgumentException("Unrecognized amount representation: " + representation); + } } diff --git a/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/MinorCurrencyUnitAmountConverter.java b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/MinorCurrencyUnitAmountConverter.java new file mode 100644 index 00000000..cd89f235 --- /dev/null +++ b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/MinorCurrencyUnitAmountConverter.java @@ -0,0 +1,34 @@ +package com.fasterxml.jackson.datatype.jodamoney; + +import org.joda.money.CurrencyUnit; +import org.joda.money.Money; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * An {@link AmountConverter} converting {@link Money} to its amount represented in + * {@link Money#getAmountMinorLong() minor units as a long} (such as {@code 1234} for + * {@code Money.parse("USD 12.34")}), and back to {@code Money} from this representation. + */ +final class MinorCurrencyUnitAmountConverter implements AmountConverter { + + private static final MinorCurrencyUnitAmountConverter INSTANCE = new MinorCurrencyUnitAmountConverter(); + + static MinorCurrencyUnitAmountConverter getInstance() { + return INSTANCE; + } + + private MinorCurrencyUnitAmountConverter() { + } + + @Override + public Long fromMoney(final Money money) { + return money.getAmountMinorLong(); + } + + @Override + public Money toMoney(final CurrencyUnit currencyUnit, final BigDecimal amount) { + return Money.of(currencyUnit, amount.movePointLeft(currencyUnit.getDecimalPlaces()), RoundingMode.UNNECESSARY); + } +} diff --git a/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/MoneyDeserializer.java b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/MoneyDeserializer.java index bcc222f6..91256242 100644 --- a/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/MoneyDeserializer.java +++ b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/MoneyDeserializer.java @@ -16,15 +16,25 @@ import org.joda.money.CurrencyUnit; import org.joda.money.Money; +import static java.util.Objects.requireNonNull; + public class MoneyDeserializer extends StdDeserializer { private static final long serialVersionUID = 1L; private final String F_AMOUNT = "amount"; private final String F_CURRENCY = "currency"; + private final AmountConverter amountConverter; + // Kept to maintain backward compatibility with 2.x + @SuppressWarnings("unused") public MoneyDeserializer() { + this(DecimalNumberAmountConverter.getInstance()); + } + + MoneyDeserializer(final AmountConverter amountConverter) { super(Money.class); + this.amountConverter = requireNonNull(amountConverter, "amount converter cannot be null"); } @Override @@ -75,7 +85,7 @@ public Money deserialize(final JsonParser p, final DeserializationContext ctxt) } else if (currencyUnit == null) { missingName = F_CURRENCY; } else { - return Money.of(currencyUnit, amount); + return amountConverter.toMoney(currencyUnit, amount); } return ctxt.reportPropertyInputMismatch(getValueType(ctxt), missingName, diff --git a/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/MoneySerializer.java b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/MoneySerializer.java index de93db0b..ed46d3c8 100644 --- a/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/MoneySerializer.java +++ b/joda-money/src/main/java/com/fasterxml/jackson/datatype/jodamoney/MoneySerializer.java @@ -1,23 +1,31 @@ package com.fasterxml.jackson.datatype.jodamoney; -import java.io.IOException; -import java.math.BigDecimal; -import java.math.RoundingMode; - -import org.joda.money.Money; - import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.type.WritableTypeId; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; +import org.joda.money.Money; + +import java.io.IOException; + +import static java.util.Objects.requireNonNull; public class MoneySerializer extends JodaMoneySerializerBase { private static final long serialVersionUID = 1L; + private final AmountConverter amountConverter; + + // Kept to maintain backward compatibility with 2.x + @SuppressWarnings("unused") public MoneySerializer() { + this(DecimalNumberAmountConverter.getInstance()); + } + + MoneySerializer(final AmountConverter amountConverter) { super(Money.class); + this.amountConverter = requireNonNull(amountConverter, "amount converter cannot be null"); } @Override @@ -45,16 +53,12 @@ public void serializeWithType(Money value, JsonGenerator g, typeSer.writeTypeSuffix(g, typeIdDef); } - private final void _writeFields(final Money money, + private void _writeFields(final Money money, final JsonGenerator g, final SerializerProvider context) throws IOException { - final BigDecimal decimal = money.getAmount(); - final int decimalPlaces = money.getCurrencyUnit().getDecimalPlaces(); - final int scale = Math.max(decimal.scale(), decimalPlaces); - g.writeNumberField("amount", decimal.setScale(scale, RoundingMode.UNNECESSARY)); - g.writeFieldName("currency"); - context.defaultSerializeValue(money.getCurrencyUnit(), g); + context.defaultSerializeField("amount", amountConverter.fromMoney(money), g); + context.defaultSerializeField("currency", money.getCurrencyUnit(), g); } } diff --git a/joda-money/src/test/java/com/fasterxml/jackson/datatype/jodamoney/ModuleTestBase.java b/joda-money/src/test/java/com/fasterxml/jackson/datatype/jodamoney/ModuleTestBase.java index 39ff7b65..e73a6344 100644 --- a/joda-money/src/test/java/com/fasterxml/jackson/datatype/jodamoney/ModuleTestBase.java +++ b/joda-money/src/test/java/com/fasterxml/jackson/datatype/jodamoney/ModuleTestBase.java @@ -1,17 +1,13 @@ package com.fasterxml.jackson.datatype.jodamoney; -import java.text.DateFormat; -import java.util.Arrays; -import java.util.TimeZone; - import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.cfg.MapperBuilder; import com.fasterxml.jackson.databind.json.JsonMapper; - import junit.framework.TestCase; -import static org.junit.Assert.*; +import java.util.Arrays; +import java.util.function.Function; public abstract class ModuleTestBase extends TestCase { @@ -30,28 +26,14 @@ protected static MapperBuilder mapperWithModuleBuilder() { .addModule(new JodaMoneyModule()); } - protected static MapperBuilder jodaMapperBuilder(DateFormat df) { - return mapperWithModuleBuilder() - .defaultDateFormat(df); - } - - protected static MapperBuilder jodaMapperBuilder(TimeZone tz) { - return mapperWithModuleBuilder() - .defaultTimeZone(tz); - } - protected static ObjectMapper mapperWithModule() { return mapperWithModuleBuilder().build(); } - protected static ObjectMapper mapperWithModule(DateFormat df) { - return jodaMapperBuilder(df) - .build(); - } - - protected static ObjectMapper mapperWithModule(TimeZone tz) { - return jodaMapperBuilder(tz) - .build(); + protected static ObjectMapper mapperWithModule(Function customizations) { + return JsonMapper.builder() + .addModule(customizations.apply(new JodaMoneyModule())) + .build(); } /* @@ -60,10 +42,6 @@ protected static ObjectMapper mapperWithModule(TimeZone tz) { /********************************************************************** */ - protected void assertEquals(int[] exp, int[] act) { - assertArrayEquals(exp, act); - } - /* /********************************************************************** /* Helper methods @@ -76,7 +54,7 @@ protected void verifyException(Throwable e, String... matches) String lmsg = (msg == null) ? "" : msg.toLowerCase(); for (String match : matches) { String lmatch = match.toLowerCase(); - if (lmsg.indexOf(lmatch) >= 0) { + if (lmsg.contains(lmatch)) { return; } } @@ -84,11 +62,7 @@ protected void verifyException(Throwable e, String... matches) +Arrays.asList(matches)+"): got one with message \""+msg+"\""); } - public String q(String str) { - return '"'+str+'"'; - } - - protected String a2q(String json) { - return json.replace("'", "\""); + protected String json(String format, Object... args) { + return String.format(format, args).replace('\'', '"'); } } diff --git a/joda-money/src/test/java/com/fasterxml/jackson/datatype/jodamoney/MoneyDeserializerTest.java b/joda-money/src/test/java/com/fasterxml/jackson/datatype/jodamoney/MoneyDeserializerTest.java index 5a638420..743af287 100644 --- a/joda-money/src/test/java/com/fasterxml/jackson/datatype/jodamoney/MoneyDeserializerTest.java +++ b/joda-money/src/test/java/com/fasterxml/jackson/datatype/jodamoney/MoneyDeserializerTest.java @@ -9,47 +9,134 @@ import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import junitparams.naming.TestCaseName; import org.joda.money.CurrencyUnit; import org.joda.money.Money; +import org.junit.Test; +import org.junit.runner.RunWith; +import static com.fasterxml.jackson.datatype.jodamoney.AmountRepresentation.*; + +@RunWith(JUnitParamsRunner.class) public final class MoneyDeserializerTest extends ModuleTestBase { - private final ObjectMapper MAPPER = mapperWithModule(); - private final ObjectReader R = MAPPER.readerFor(Money.class); /* /********************************************************************** /* Tests, happy path /********************************************************************** */ - - public void testShouldDeserialize() throws IOException - { - - final String content = "{\"amount\":19.99,\"currency\":\"EUR\"}"; - final Money actualAmount = R.readValue(content); - assertEquals(Money.of(CurrencyUnit.EUR, BigDecimal.valueOf(19.99)), actualAmount); - assertEquals(BigDecimal.valueOf(19.99), actualAmount.getAmount()); - assertEquals(actualAmount.getCurrencyUnit().getCode(), "EUR"); + @Test + @Parameters({ + "{'amount':19.99\\,'currency':'EUR'}, EUR, 19.99", + "{'amount':19.999\\,'currency':'KWD'}, KWD, 19.999", + "{'amount':19\\,'currency':'JPY'}, JPY, 19", + "{'amount':19.9\\,'currency':'EUR'}, EUR, 19.90", + "{'amount':-19.5\\,'currency':'EUR'}, EUR, -19.50", + "{'amount':0\\,'currency':'EUR'}, EUR, 0", + "{'amount':'19.99'\\,'currency':'EUR'}, EUR, 19.99", + "{'amount':'19.0'\\,'currency':'EUR'}, EUR, 19.00", + "{'amount':'19'\\,'currency':'EUR'}, EUR, 19.00", + "{'currency':'EUR'\\,'amount':'19.50'}, EUR, 19.50", + }) + @TestCaseName("should deserialize {0} as {1} {2}") + public void testShouldDeserialize( + String json, + String currencyCode, + BigDecimal amount + ) throws Exception { + + final ObjectMapper mapper = mapperWithModule(); + final ObjectReader reader = mapper.readerFor(Money.class); + + final Money actual = reader.readValue(json(json)); + + assertEquals(Money.of(CurrencyUnit.of(currencyCode), amount), actual); } - public void testShouldDeserializeWhenAmountIsAStringValue() throws IOException - { - final String content = "{\"currency\":\"EUR\",\"amount\":\"19.99\"}"; - final Money actualAmount = R.readValue(content); - - assertEquals(BigDecimal.valueOf(19.99), actualAmount.getAmount()); - assertEquals(actualAmount.getCurrencyUnit().getCode(), "EUR"); + @Test + @Parameters({ + "{'amount':19.99\\,'currency':'EUR'}, EUR, 19.99", + "{'amount':19.999\\,'currency':'KWD'}, KWD, 19.999", + "{'amount':19\\,'currency':'JPY'}, JPY, 19", + "{'amount':19.9\\,'currency':'EUR'}, EUR, 19.90", + "{'amount':-19.5\\,'currency':'EUR'}, EUR, -19.50", + "{'amount':0\\,'currency':'EUR'}, EUR, 0", + "{'amount':'19.99'\\,'currency':'EUR'}, EUR, 19.99", + "{'amount':'19.0'\\,'currency':'EUR'}, EUR, 19.00", + "{'amount':'19'\\,'currency':'EUR'}, EUR, 19.00", + "{'currency':'EUR'\\,'amount':'19.50'}, EUR, 19.50", + }) + @TestCaseName("should deserialize {0} as {1} {2}") + public void testShouldDeserializeDecimalNumberAmount( + String json, + String currencyCode, + BigDecimal amount + ) throws Exception { + + final ObjectMapper mapper = mapperWithModule(m -> m.withAmountRepresentation(DECIMAL_NUMBER)); + final ObjectReader reader = mapper.readerFor(Money.class); + + final Money actual = reader.readValue(json(json)); + + assertEquals(Money.of(CurrencyUnit.of(currencyCode), amount), actual); } - public void testShouldDeserializeWhenOrderIsDifferent() throws IOException - { - final String content = "{\"currency\":\"EUR\",\"amount\":19.99}"; - final Money actualAmount = R.readValue(content); + @Test + @Parameters({ + "{'amount':'19.99'\\,'currency':'EUR'}, EUR, 19.99", + "{'amount':'19.999'\\,'currency':'KWD'}, KWD, 19.999", + "{'amount':'19'\\,'currency':'JPY'}, JPY, 19", + "{'amount':'19.9'\\,'currency':'EUR'}, EUR, 19.90", + "{'amount':'-19.5'\\,'currency':'EUR'}, EUR, -19.50", + "{'amount':'0'\\,'currency':'EUR'}, EUR, 0", + "{'amount':'19.0'\\,'currency':'EUR'}, EUR, 19.00", + "{'amount':'19'\\,'currency':'EUR'}, EUR, 19.00", + "{'currency':'EUR'\\,'amount':'19.50'}, EUR, 19.50", + }) + @TestCaseName("should deserialize {0} as {1} {2}") + public void testShouldDeserializeDecimalStringAmount( + String json, + String currencyCode, + BigDecimal amount + ) throws Exception { + + final ObjectMapper mapper = mapperWithModule(m -> m.withAmountRepresentation(DECIMAL_STRING)); + final ObjectReader reader = mapper.readerFor(Money.class); + + final Money actual = reader.readValue(json(json)); + + assertEquals(Money.of(CurrencyUnit.of(currencyCode), amount), actual); + } - assertEquals(BigDecimal.valueOf(19.99), actualAmount.getAmount()); - assertEquals(actualAmount.getCurrencyUnit().getCode(), "EUR"); + @Test + @Parameters({ + "{'amount':1999\\,'currency':'EUR'}, EUR, 19.99", + "{'amount':19999\\,'currency':'KWD'}, KWD, 19.999", + "{'amount':19\\,'currency':'JPY'}, JPY, 19", + "{'amount':1990\\,'currency':'EUR'}, EUR, 19.90", + "{'amount':-1950\\,'currency':'EUR'}, EUR, -19.50", + "{'amount':0\\,'currency':'EUR'}, EUR, 0", + "{'amount':'-1950'\\,'currency':'EUR'}, EUR, -19.50", + "{'amount':'1900.00'\\,'currency':'EUR'}, EUR, 19.00", + "{'currency':'EUR'\\,'amount':1950}, EUR, 19.50", + }) + @TestCaseName("should deserialize {0} as {1} {2}") + public void testShouldDeserializeAmountInMinorCurrencyUnit( + String json, + String currencyCode, + BigDecimal amount + ) throws Exception { + + final ObjectMapper mapper = mapperWithModule(m -> m.withAmountRepresentation(MINOR_CURRENCY_UNIT)); + final ObjectReader reader = mapper.readerFor(Money.class); + + final Money actual = reader.readValue(json(json)); + + assertEquals(Money.of(CurrencyUnit.of(currencyCode), amount), actual); } /* @@ -58,6 +145,9 @@ public void testShouldDeserializeWhenOrderIsDifferent() throws IOException /********************************************************************** */ + private final ObjectMapper MAPPER = mapperWithModule(); + private final ObjectReader R = MAPPER.readerFor(Money.class); + public void testShouldFailDeserializationWithoutAmount() throws Exception { final String content = "{\"currency\":\"EUR\"}"; @@ -101,7 +191,7 @@ public void testShouldPerformDeserializationWithUnknownProperties() throws IOExc { final ObjectReader r = MAPPER.readerFor(Money.class) .without(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - final String content = a2q("{'amount':5000,'currency':'EUR','unknown':'test'}"); + final String content = json("{'amount':5000,'currency':'EUR','unknown':'test'}"); final Money actualAmount = r.readValue(content); assertEquals(Money.of(CurrencyUnit.EUR, BigDecimal.valueOf(5000)), actualAmount); } diff --git a/joda-money/src/test/java/com/fasterxml/jackson/datatype/jodamoney/MoneySerializerTest.java b/joda-money/src/test/java/com/fasterxml/jackson/datatype/jodamoney/MoneySerializerTest.java index d53be992..0401c08e 100644 --- a/joda-money/src/test/java/com/fasterxml/jackson/datatype/jodamoney/MoneySerializerTest.java +++ b/joda-money/src/test/java/com/fasterxml/jackson/datatype/jodamoney/MoneySerializerTest.java @@ -4,14 +4,105 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import junitparams.naming.TestCaseName; import org.joda.money.CurrencyUnit; import org.joda.money.Money; +import org.junit.Test; +import org.junit.runner.RunWith; +import static com.fasterxml.jackson.datatype.jodamoney.AmountRepresentation.*; + +@RunWith(JUnitParamsRunner.class) public final class MoneySerializerTest extends ModuleTestBase { - public void testShouldSerialize() throws Exception { + + @Test + @Parameters({ + "EUR, 19.99, 19.99", + "KWD, 19.999, 19.999", + "JPY, 19, 19", + "EUR, 19.9, 19.90", + "EUR, -19.5, -19.50", + "EUR, 0, 0.00", + }) + @TestCaseName("should serialize {0} {1} with amount representation {2}") + public void testShouldSerialize( + String currencyCode, + BigDecimal amount, + String amountInJson + ) throws Exception { + final ObjectMapper mapper = mapperWithModule(); - assertEquals("{\"amount\":19.99,\"currency\":\"EUR\"}", - mapper.writeValueAsString(Money.of(CurrencyUnit.EUR, BigDecimal.valueOf(19.99)))); + + assertEquals(json("{'amount':%s,'currency':'%s'}", amountInJson, currencyCode), + mapper.writeValueAsString(Money.of(CurrencyUnit.of(currencyCode), amount))); + } + + @Test + @Parameters({ + "EUR, 19.99, 19.99", + "KWD, 19.999, 19.999", + "JPY, 19, 19", + "EUR, 19.9, 19.90", + "EUR, -19.5, -19.50", + "EUR, 0, 0.00", + }) + @TestCaseName("should serialize {0} {1} with amount representation {2}") + public void testShouldSerializeAmountAsDecimalNumber( + String currencyCode, + BigDecimal amount, + String amountInJson + ) throws Exception { + + final ObjectMapper mapper = mapperWithModule(m -> m.withAmountRepresentation(DECIMAL_NUMBER)); + + assertEquals(json("{'amount':%s,'currency':'%s'}", amountInJson, currencyCode), + mapper.writeValueAsString(Money.of(CurrencyUnit.of(currencyCode), amount))); + } + + @Test + @Parameters({ + "EUR, 19.99, 19.99", + "KWD, 19.999, 19.999", + "JPY, 19, 19", + "EUR, 19.9, 19.90", + "EUR, -19.5, -19.50", + "EUR, 0, 0.00", + }) + @TestCaseName("should serialize {0} {1} with amount representation {2}") + public void testShouldSerializeAmountAsDecimalString( + String currencyCode, + BigDecimal amount, + String amountInJson + ) throws Exception { + + final ObjectMapper mapper = mapperWithModule(m -> m.withAmountRepresentation(DECIMAL_STRING)); + + assertEquals(json("{'amount':'%s','currency':'%s'}", amountInJson, currencyCode), + mapper.writeValueAsString(Money.of(CurrencyUnit.of(currencyCode), amount))); + } + + @Test + @Parameters({ + "EUR, 19.99, 1999", + "KWD, 19.999, 19999", + "JPY, 19, 19", + "EUR, 19.9, 1990", + "EUR, -19.5, -1950", + "EUR, 0, 0", + }) + @TestCaseName("should serialize {0} {1} with amount representation {2}") + public void testShouldSerializeAmountInMinorCurrencyUnit( + String currencyCode, + BigDecimal amount, + String amountInJson + ) throws Exception { + + final ObjectMapper mapper = mapperWithModule(m -> m.withAmountRepresentation(MINOR_CURRENCY_UNIT)); + + assertEquals(json("{'amount':%s,'currency':'%s'}", amountInJson, currencyCode), + mapper.writeValueAsString(Money.of(CurrencyUnit.of(currencyCode), amount))); } }