Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
abaf36d
Migrating Commit 7fd4c1e from zalando/jackson-datatype-money
sri-adarsh-kumar Oct 17, 2024
8d99b6d
Respond to review comments
sri-adarsh-kumar Oct 28, 2024
487cbf9
Move Money.md in from source repo
sri-adarsh-kumar Oct 29, 2024
f347a4c
Use hand-crafted moditect date and change ObjectMapper in test
sri-adarsh-kumar Nov 5, 2024
90bd5ae
Rename MoneyModule, use addModule in ReadMe, update License year
sri-adarsh-kumar Jan 23, 2025
5fa5d6a
Remove strict Moneta dependency, Update docs
sri-adarsh-kumar Jan 27, 2025
bc9ab3a
Add WIP Moneta Module
sri-adarsh-kumar Jan 29, 2025
4df053d
Add WIP Moneta Module
sri-adarsh-kumar Jan 29, 2025
01ef594
Merge branch '2.19' into 2.19
cowtowncoder Jan 31, 2025
3d34f47
Add WIP Moneta Module
sri-adarsh-kumar Jan 31, 2025
4a6a7fc
Merge branch '2.19' of https://github.com/sri-adarsh-kumar/jackson-da…
sri-adarsh-kumar Jan 31, 2025
cf26624
Review docs
sri-adarsh-kumar Jan 31, 2025
a8c7b2e
Update parent README
sri-adarsh-kumar Jan 31, 2025
6948585
Use SPI for MonetaryAmountFactory
sri-adarsh-kumar Feb 3, 2025
ed52822
Deserialize MonetaryAmount with Amount Factory
sri-adarsh-kumar Feb 4, 2025
7e75b5f
Migrate Javax Money and Moneta modules to Junit 5
sri-adarsh-kumar Feb 4, 2025
90a4850
Support for Moneta types moved from Javax to Moneta Module
sri-adarsh-kumar Feb 4, 2025
045a019
Remove Amount Factory from MonetaModule
sri-adarsh-kumar Feb 5, 2025
8cded1d
Remove CurrencyUnit deserializing from MonetaModule
sri-adarsh-kumar Feb 5, 2025
ac80e2a
Merge branch '2.19' into 2.19
cowtowncoder Mar 17, 2025
a1978c2
Fix typo in Moneta Readme
sri-adarsh-kumar Mar 18, 2025
9fb4b7e
Bit of refactoring
cowtowncoder Mar 20, 2025
1455f75
Fixing things wrt refactoring
cowtowncoder Mar 20, 2025
cdcc25a
Merge branch '2.19' into 2.19
cowtowncoder Mar 20, 2025
1945ac3
minor reording of README
cowtowncoder Mar 20, 2025
b6ca468
Merge branch '2.19' of github.com:sri-adarsh-kumar/jackson-datatypes-…
cowtowncoder Mar 20, 2025
a47a085
Merge branch '2.19' into 2.19
cowtowncoder Mar 20, 2025
7b8cfb3
Minor to fix to `module-info.java`s
cowtowncoder Mar 20, 2025
4c2fd28
Minor fixes to READMEs
cowtowncoder Mar 20, 2025
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
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ datatype modules to support 3rd party libraries.

Currently included are:

* [jackson-datatype-jakarta-mail](jakarta-mail/) for Jakarta Mail (ex-Java Mail) (starting with Jackson 2.13)
* Currently (2.13) just type `jakarta.mail.internet.InternetAddress`
* [jackson-datatype-javax-money](javax-money/) for [JSR 354](https://github.com/JavaMoney/jsr354-api) datatypes (starting with Jackson 2.19)
* [jackson-datatype-moneta](moneta/) for [JavaMoney Moneta RI](https://javamoney.github.io/) datatypes (jsr354 reference implementation) (starting with Jackson 2.19)
* [jackson-datatype-joda-money](joda-money/) for [Joda-Money](https://www.joda.org/joda-money/) datatypes
* JSR-353/JSON-P: 2 variants (starting with Jackson 2.12.2)
* [jackson-datatype-jsr353](jsr-353/) for older "javax.json" [JSR-353](https://www.jcp.org/en/jsr/detail?id=353) (aka JSON-P) datatypes (package `javax.json`)
* [jackson-datatype-jakarta-jsonp](jakarta-jsonp/) for newer "Jakarta" JSON-P datatypes (package `jakarta.json`)
* [jackson-datatype-json-org](json-org/) for ([org.json](http://json.org/java)) JSON model datatypes (included in Android SDK, as well as stand-alone Java library)
* [jackson-datatype-jakarta-mail](jakarta-mail/) for Jakarta Mail (ex-Java Mail) (starting with Jackson 2.13)
* Currently (2.13) just type `jakarta.mail.internet.InternetAddress`

Note that this repo was created for Jackson 2.11: prior to this, individual datatype
modules had their own repositories.
Expand Down Expand Up @@ -67,6 +69,9 @@ ObjectMapper mapper = JsonMapper.builder()
.addModule(new JsonOrgModule())
.addModule(new JodaMoneyModule())
// ONE of these (not both):
.addModule(new JavaxMoneyModule())
.addModule(new MonetaMoneyModule())
// ONE of these (not both):
.addModule(new JSR353Module()) // old (javax) json-p API
.addModule(new JSONPModule()) // new (jakarta) json-P API
.build();
Expand Down
118 changes: 118 additions & 0 deletions javax-money/MONEY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Representing Money in JSON

> A large proportion of the computers in this world manipulate money, so it's always puzzled me that money isn't actually a first class data type in any mainstream programming language.
>
> [Martin Fowler](https://martinfowler.com/eaaCatalog/money.html)

Unfortunately JSON is no different. This document tries to change that by proposing and comparing different styles to represent money, some inspired by external sources and some based on our own experience.

## ⚠️ Monetary amounts ≠ floats

Before we dive into details, always keep the following in mind. However you desire to format money in JSON, nothing changes the fact that you should...

> **Never hold monetary values [..] in a float variable.** Floating point is not suitable for this work, and you must use either [fixed-point](#fixed-point) or [decimal](#decimal) values.
>
> [Coinkite: Common Terms and Data Objects](https://web.archive.org/web/20150924073850/https://docs.coinkite.com/api/common.html)

## Styles

We identified the following styles that all of different advantages and disadvantages that are discussed in their respective section.

| Style | Expressive | Arithmetic | Pitfalls / Misuses |
|------------------------------------|------------|------------|--------------------|
| [Decimal](#decimal) | ✔ | ✔ | Precision |
| [Quoted Decimal](#quoted-decimal) | ✔ | ✘ | Parsing |
| [Fixed Point](#fixed-point) | ✘ | ✔ | Mixed scales |
| [Mixed](#mixed) | ✘ | ✔ | Consistency |

### Decimal

The most straightforward way to represent a monetary amount would be a base-10 decimal number:

```json
{
"amount": 49.95,
"currency": "EUR"
}
```

It's expressive, readable and allows arithmetic operations. The downside is that most [JSON decoders will treat it as a floating point](https://tools.ietf.org/html/rfc7159#section-6) number which is very much undesirable.

Most programming languages have support for arbitrary-precision [decimals](#decimal-implementations) and JSON decoders that can be configured to use them. In general it can be considered to be a problem of the implementation, not the format itself.

### Quoted Decimal

Same as [Decimal](#decimal) but quoted so your JSON decoder treats it as a string:

```json
{
"amount": "49.95",
"currency": "EUR"
}
```

It solves the precision problem of decimals on the expense of performing arithmetic operations on it. It also requires a two-phase parsing, i.e. parsing the JSON text into a data structure and then parsing quoted amounts into decimals.

### Fixed Point

> A value of a fixed-point data type is essentially an integer that is scaled by an implicit specific factor determined by the type.
>
> [Wikipedia: Fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic)

```json
{
"amount": 4995,
"currency": "EUR"
}
```

The implicit scaling factor is defined as (0.1 raised to the power of) the currency's [default number of fraction digits](http://www.localeplanet.com/icu/currency.html).

In rare cases one might need a higher precision, e.g. to have sub-cent. In this case the scale can be defined explicitly:

```json
{
"amount": 499599,
"currency": "EUR",
"scale": 4
}
```

The downside with fixed-point amounts is that reading them is a bit harder and arithmetic with mixed scale amounts can be tricky and error-prone.

### Mixed

As a way to counter all negative aspects of the styles above one idea would be to have a single object that contains all of the formats:

```json
{
"decimal": 49.95,
"quoted_decimal": "49.95",
"fixed_point": 4995,
"scale": 2,
"currency": "EUR"
}
```

Decoders can choose the representation that fits them the best. Encoders on the other hand have the harder task by providing all of them and making sure that all values are in fact consistent.

## Decimal Implementations

| Language | Implementation |
|------------|---------------------------------------------------------------------------------------------|
| C# | [decimal](https://msdn.microsoft.com/en-us/library/364x0z75.aspx) |
| Java | [java.math.BigDecimal](https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html) |
| JavaScript | [decimal.js](https://github.com/MikeMcl/decimal.js/) |
| Python | [decimal.Decimal](https://docs.python.org/2/library/decimal.html) |

## Credits and References

- [Coinkite: Currency Amounts](https://web.archive.org/web/20150924073850/https://docs.coinkite.com/api/common.html#currency-amounts)
- [Culttt: How to handle money and currency in web applications](http://culttt.com/2014/05/28/handle-money-currency-web-applications/)
- [Currency codes - ISO 4217](https://www.iso.org/iso-4217-currency-codes.html)
- [LocalePlanet: ICU Currencies](http://www.localeplanet.com/icu/currency.html)
- [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange Format](https://tools.ietf.org/html/rfc7159#section-6)
- [Stackoverflow: What is the standard for formatting currency values in JSON?](http://stackoverflow.com/questions/30249406/what-is-the-standard-for-formatting-currency-values-in-json)
- [Stackoverflow: Why not use Double or Float to represent currency?](http://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency/3730040#3730040)
- [TechEmpower: Mangling JSON numbers](https://www.techempower.com/blog/2016/07/05/mangling-json-numbers/)
- [Wikipedia: Fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic)
207 changes: 207 additions & 0 deletions javax-money/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# Jackson Datatype Javax-Money

*Jackson Datatype Javax-Money* is a [Jackson](https://github.com/FasterXML/jackson) module to support JSON serialization and deserialization of [JSR 354](https://github.com/JavaMoney/jsr354-api) data types.
It fills a niche, in that it integrates [MonetaryAmount](https://javamoney.github.io/apidocs/javax/money/MonetaryAmount.html) and Jackson so that they work seamlessly together, without requiring additional
developer effort. In doing so, it aims to perform a small but repetitive task — once and for all.

This library reflects an opinionated API [representation of monetary amounts in JSON](MONEY.md)

With this library, it is possible to represent monetary amounts in JSON as follows:

```json
{
"amount": 29.95,
"currency": "EUR"
}
```

## Features

- enables you to express monetary amounts in JSON
- can be used in a REST APIs
- customized field names
- localization of formatted monetary amounts
- allows you to implement RESTful API endpoints that format monetary amounts based on the Accept-Language header
- is unique and flexible

## Dependencies

- Java 8 or higher
- Any build tool using Maven Central, or direct download
- Jackson

## Installation

Add the following dependency to your project:

```xml

<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-javax-money</artifactId>
<version>${jackson-datatype-money.version}</version>
</dependency>
```

For ultimate flexibility, this module is compatible with any implementation of JSR 354 MonetaryAmount

## Configuration

Register the module with your `ObjectMapper`:

```java
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JavaxMoneyModule())
.build();
```

Alternatively, you can use the SPI capabilities:

```java
ObjectMapper mapper = new ObjectMapper()
.findAndRegisterModules();
```

### Serialization

For serialization this module currently supports
[
`javax.money.MonetaryAmount`](https://github.com/JavaMoney/jsr354-api/blob/master/src/main/java/javax/money/MonetaryAmount.java)
and will, by default, serialize it as:

```json
{
"amount": 99.95,
"currency": "EUR"
}
```

To serialize number as a JSON string, you have to configure the quoted decimal number value serializer:

```java
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JavaxMoneyModule().withQuotedDecimalNumbers())
.build();
```

```json
{
"amount": "99.95",
"currency": "EUR"
}
```

### Formatting

A special feature for serializing monetary amounts is *formatting*, which is **disabled by default**. To enable it, you
have to either enable default formatting:

```java
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JavaxMoneyModule().withDefaultFormatting())
.build();
```

... or pass in a `MonetaryAmountFormatFactory` implementation to the `JavaxMoneyModule`:

```java
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JavaxMoneyModule()
.withFormatting(new CustomMonetaryAmountFormatFactory()))
.build();
```

The default formatting delegates directly to `MonetaryFormats.getAmountFormat(Locale, String...)`.

Formatting only affects the serialization and can be customized based on the *current* locale, as defined by the
[
`SerializationConfig`](https://fasterxml.github.io/jackson-databind/javadoc/2.0.0/com/fasterxml/jackson/databind/SerializationConfig.html#with\(java.util.Locale\)).
This allows to implement RESTful API endpoints
that format monetary amounts based on the `Accept-Language` header.

The first example serializes a monetary amount using the `de_DE` locale:

```java
ObjectWriter writer = mapper.writer().with(Locale.GERMANY);
writer.writeValueAsString(Money.of(29.95, "EUR"));
```

```json
{
"amount": 29.95,
"currency": "EUR",
"formatted": "29,95 EUR"
}
```

The following example uses `en_US`:

```java
ObjectWriter writer = mapper.writer().with(Locale.US);
writer.writeValueAsString(Money.of(29.95, "USD"));
```

```json
{
"amount": 29.95,
"currency": "USD",
"formatted": "USD29.95"
}
```

More sophisticated formatting rules can be supported by implementing `MonetaryAmountFormatFactory` directly.

### Deserialization

This module will not have a default deserialization feature.
At the same time, if the [Moneta](https://javamoney.github.io/ri.html) library is found in the class path, the module will use `org.javamoney.moneta.Money` as an implementation for `javax.money.MonetaryAmount` by default when deserializing monetary amounts.

Alternatively, in order to deserialize money values, one has to configure the module to use a specific implementation of `javax.money.MonetaryAmount`.
This can be done by passing the required `MonetaryAmountFactory` to the `JavaxMoneyModule`:

```java
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JavaxMoneyModule()
.withMonetaryAmountFactory(new CustomMonetaryAmountFactory()))
.build();
```

You can also pass in a method reference:

```java
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JavaxMoneyModule()
.withMonetaryAmountFactory(FastMoney::of))
.build();
```

Please note that, for Moneta implementations like Money, FastMoney and RoundedMoney, the sibling module `jackson-datatype-moneta` can also be used.
Refer to [javax-money-moneta](../javax-money-moneta/README.md) for more information.

### Custom Field Names

As you have seen in the previous examples the `JavaxMoneyModule` uses the field names `amount`, `currency` and `formatted`
by default. Those names can be overridden if desired:

```java
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JavaxMoneyModule()
.withAmountFieldName("value")
.withCurrencyFieldName("unit")
.withFormattedFieldName("pretty"))
.build();
```

## Usage

After registering and configuring the module you're now free to directly use `MonetaryAmount` in your data types:

```java
import javax.money.MonetaryAmount;

public class Product {
private String sku;
private MonetaryAmount price;
...
}
```
Loading