Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
18 changes: 18 additions & 0 deletions joda-money/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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();
```
6 changes: 6 additions & 0 deletions joda-money/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>pl.pragmatists</groupId>
<artifactId>JUnitParams</artifactId>
<version>1.1.1</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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,
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Money>
{
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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Money>
{
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
Expand Down Expand Up @@ -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);
}
}
Loading