Skip to content

Commit 77de166

Browse files
Move jackson-datatype-money module from zalando (#48)
* Migrating Commit 7fd4c1e from zalando/jackson-datatype-money
1 parent 27583e5 commit 77de166

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3311
-3
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ datatype modules to support 3rd party libraries.
55

66
Currently included are:
77

8+
* [jackson-datatype-jakarta-mail](jakarta-mail/) for Jakarta Mail (ex-Java Mail) (starting with Jackson 2.13)
9+
* Currently (2.13) just type `jakarta.mail.internet.InternetAddress`
10+
* [jackson-datatype-javax-money](javax-money/) for [JSR 354](https://github.com/JavaMoney/jsr354-api) datatypes (starting with Jackson 2.19)
11+
* [jackson-datatype-moneta](moneta/) for [JavaMoney Moneta RI](https://javamoney.github.io/) datatypes (jsr354 reference implementation) (starting with Jackson 2.19)
812
* [jackson-datatype-joda-money](joda-money/) for [Joda-Money](https://www.joda.org/joda-money/) datatypes
913
* JSR-353/JSON-P: 2 variants (starting with Jackson 2.12.2)
1014
* [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`)
1115
* [jackson-datatype-jakarta-jsonp](jakarta-jsonp/) for newer "Jakarta" JSON-P datatypes (package `jakarta.json`)
1216
* [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)
13-
* [jackson-datatype-jakarta-mail](jakarta-mail/) for Jakarta Mail (ex-Java Mail) (starting with Jackson 2.13)
14-
* Currently (2.13) just type `jakarta.mail.internet.InternetAddress`
1517

1618
Note that this repo was created for Jackson 2.11: prior to this, individual datatype
1719
modules had their own repositories.
@@ -67,6 +69,9 @@ ObjectMapper mapper = JsonMapper.builder()
6769
.addModule(new JsonOrgModule())
6870
.addModule(new JodaMoneyModule())
6971
// ONE of these (not both):
72+
.addModule(new JavaxMoneyModule())
73+
.addModule(new MonetaMoneyModule())
74+
// ONE of these (not both):
7075
.addModule(new JSR353Module()) // old (javax) json-p API
7176
.addModule(new JSONPModule()) // new (jakarta) json-P API
7277
.build();

javax-money/MONEY.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Representing Money in JSON
2+
3+
> 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.
4+
>
5+
> [Martin Fowler](https://martinfowler.com/eaaCatalog/money.html)
6+
7+
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.
8+
9+
## ⚠️ Monetary amounts ≠ floats
10+
11+
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...
12+
13+
> **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.
14+
>
15+
> [Coinkite: Common Terms and Data Objects](https://web.archive.org/web/20150924073850/https://docs.coinkite.com/api/common.html)
16+
17+
## Styles
18+
19+
We identified the following styles that all of different advantages and disadvantages that are discussed in their respective section.
20+
21+
| Style | Expressive | Arithmetic | Pitfalls / Misuses |
22+
|------------------------------------|------------|------------|--------------------|
23+
| [Decimal](#decimal) ||| Precision |
24+
| [Quoted Decimal](#quoted-decimal) ||| Parsing |
25+
| [Fixed Point](#fixed-point) ||| Mixed scales |
26+
| [Mixed](#mixed) ||| Consistency |
27+
28+
### Decimal
29+
30+
The most straightforward way to represent a monetary amount would be a base-10 decimal number:
31+
32+
```json
33+
{
34+
"amount": 49.95,
35+
"currency": "EUR"
36+
}
37+
```
38+
39+
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.
40+
41+
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.
42+
43+
### Quoted Decimal
44+
45+
Same as [Decimal](#decimal) but quoted so your JSON decoder treats it as a string:
46+
47+
```json
48+
{
49+
"amount": "49.95",
50+
"currency": "EUR"
51+
}
52+
```
53+
54+
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.
55+
56+
### Fixed Point
57+
58+
> A value of a fixed-point data type is essentially an integer that is scaled by an implicit specific factor determined by the type.
59+
>
60+
> [Wikipedia: Fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic)
61+
62+
```json
63+
{
64+
"amount": 4995,
65+
"currency": "EUR"
66+
}
67+
```
68+
69+
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).
70+
71+
In rare cases one might need a higher precision, e.g. to have sub-cent. In this case the scale can be defined explicitly:
72+
73+
```json
74+
{
75+
"amount": 499599,
76+
"currency": "EUR",
77+
"scale": 4
78+
}
79+
```
80+
81+
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.
82+
83+
### Mixed
84+
85+
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:
86+
87+
```json
88+
{
89+
"decimal": 49.95,
90+
"quoted_decimal": "49.95",
91+
"fixed_point": 4995,
92+
"scale": 2,
93+
"currency": "EUR"
94+
}
95+
```
96+
97+
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.
98+
99+
## Decimal Implementations
100+
101+
| Language | Implementation |
102+
|------------|---------------------------------------------------------------------------------------------|
103+
| C# | [decimal](https://msdn.microsoft.com/en-us/library/364x0z75.aspx) |
104+
| Java | [java.math.BigDecimal](https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html) |
105+
| JavaScript | [decimal.js](https://github.com/MikeMcl/decimal.js/) |
106+
| Python | [decimal.Decimal](https://docs.python.org/2/library/decimal.html) |
107+
108+
## Credits and References
109+
110+
- [Coinkite: Currency Amounts](https://web.archive.org/web/20150924073850/https://docs.coinkite.com/api/common.html#currency-amounts)
111+
- [Culttt: How to handle money and currency in web applications](http://culttt.com/2014/05/28/handle-money-currency-web-applications/)
112+
- [Currency codes - ISO 4217](https://www.iso.org/iso-4217-currency-codes.html)
113+
- [LocalePlanet: ICU Currencies](http://www.localeplanet.com/icu/currency.html)
114+
- [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange Format](https://tools.ietf.org/html/rfc7159#section-6)
115+
- [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)
116+
- [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)
117+
- [TechEmpower: Mangling JSON numbers](https://www.techempower.com/blog/2016/07/05/mangling-json-numbers/)
118+
- [Wikipedia: Fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic)

javax-money/README.md

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# Jackson Datatype Javax-Money
2+
3+
*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.
4+
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
5+
developer effort. In doing so, it aims to perform a small but repetitive task — once and for all.
6+
7+
This library reflects an opinionated API [representation of monetary amounts in JSON](MONEY.md)
8+
9+
With this library, it is possible to represent monetary amounts in JSON as follows:
10+
11+
```json
12+
{
13+
"amount": 29.95,
14+
"currency": "EUR"
15+
}
16+
```
17+
18+
## Features
19+
20+
- enables you to express monetary amounts in JSON
21+
- can be used in a REST APIs
22+
- customized field names
23+
- localization of formatted monetary amounts
24+
- allows you to implement RESTful API endpoints that format monetary amounts based on the Accept-Language header
25+
- is unique and flexible
26+
27+
## Dependencies
28+
29+
- Java 8 or higher
30+
- Any build tool using Maven Central, or direct download
31+
- Jackson
32+
33+
## Installation
34+
35+
Add the following dependency to your project:
36+
37+
```xml
38+
39+
<dependency>
40+
<groupId>com.fasterxml.jackson.datatype</groupId>
41+
<artifactId>jackson-datatype-javax-money</artifactId>
42+
<version>${jackson-datatype-money.version}</version>
43+
</dependency>
44+
```
45+
46+
For ultimate flexibility, this module is compatible with any implementation of JSR 354 MonetaryAmount
47+
48+
## Configuration
49+
50+
Register the module with your `ObjectMapper`:
51+
52+
```java
53+
ObjectMapper mapper = JsonMapper.builder()
54+
.addModule(new JavaxMoneyModule())
55+
.build();
56+
```
57+
58+
Alternatively, you can use the SPI capabilities:
59+
60+
```java
61+
ObjectMapper mapper = new ObjectMapper()
62+
.findAndRegisterModules();
63+
```
64+
65+
### Serialization
66+
67+
For serialization this module currently supports
68+
[
69+
`javax.money.MonetaryAmount`](https://github.com/JavaMoney/jsr354-api/blob/master/src/main/java/javax/money/MonetaryAmount.java)
70+
and will, by default, serialize it as:
71+
72+
```json
73+
{
74+
"amount": 99.95,
75+
"currency": "EUR"
76+
}
77+
```
78+
79+
To serialize number as a JSON string, you have to configure the quoted decimal number value serializer:
80+
81+
```java
82+
ObjectMapper mapper = JsonMapper.builder()
83+
.addModule(new JavaxMoneyModule().withQuotedDecimalNumbers())
84+
.build();
85+
```
86+
87+
```json
88+
{
89+
"amount": "99.95",
90+
"currency": "EUR"
91+
}
92+
```
93+
94+
### Formatting
95+
96+
A special feature for serializing monetary amounts is *formatting*, which is **disabled by default**. To enable it, you
97+
have to either enable default formatting:
98+
99+
```java
100+
ObjectMapper mapper = JsonMapper.builder()
101+
.addModule(new JavaxMoneyModule().withDefaultFormatting())
102+
.build();
103+
```
104+
105+
... or pass in a `MonetaryAmountFormatFactory` implementation to the `JavaxMoneyModule`:
106+
107+
```java
108+
ObjectMapper mapper = JsonMapper.builder()
109+
.addModule(new JavaxMoneyModule()
110+
.withFormatting(new CustomMonetaryAmountFormatFactory()))
111+
.build();
112+
```
113+
114+
The default formatting delegates directly to `MonetaryFormats.getAmountFormat(Locale, String...)`.
115+
116+
Formatting only affects the serialization and can be customized based on the *current* locale, as defined by the
117+
[
118+
`SerializationConfig`](https://fasterxml.github.io/jackson-databind/javadoc/2.0.0/com/fasterxml/jackson/databind/SerializationConfig.html#with\(java.util.Locale\)).
119+
This allows to implement RESTful API endpoints
120+
that format monetary amounts based on the `Accept-Language` header.
121+
122+
The first example serializes a monetary amount using the `de_DE` locale:
123+
124+
```java
125+
ObjectWriter writer = mapper.writer().with(Locale.GERMANY);
126+
writer.writeValueAsString(Money.of(29.95, "EUR"));
127+
```
128+
129+
```json
130+
{
131+
"amount": 29.95,
132+
"currency": "EUR",
133+
"formatted": "29,95 EUR"
134+
}
135+
```
136+
137+
The following example uses `en_US`:
138+
139+
```java
140+
ObjectWriter writer = mapper.writer().with(Locale.US);
141+
writer.writeValueAsString(Money.of(29.95, "USD"));
142+
```
143+
144+
```json
145+
{
146+
"amount": 29.95,
147+
"currency": "USD",
148+
"formatted": "USD29.95"
149+
}
150+
```
151+
152+
More sophisticated formatting rules can be supported by implementing `MonetaryAmountFormatFactory` directly.
153+
154+
### Deserialization
155+
156+
This module will not have a default deserialization feature.
157+
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.
158+
159+
Alternatively, in order to deserialize money values, one has to configure the module to use a specific implementation of `javax.money.MonetaryAmount`.
160+
This can be done by passing the required `MonetaryAmountFactory` to the `JavaxMoneyModule`:
161+
162+
```java
163+
ObjectMapper mapper = JsonMapper.builder()
164+
.addModule(new JavaxMoneyModule()
165+
.withMonetaryAmountFactory(new CustomMonetaryAmountFactory()))
166+
.build();
167+
```
168+
169+
You can also pass in a method reference:
170+
171+
```java
172+
ObjectMapper mapper = JsonMapper.builder()
173+
.addModule(new JavaxMoneyModule()
174+
.withMonetaryAmountFactory(FastMoney::of))
175+
.build();
176+
```
177+
178+
Please note that, for Moneta implementations like Money, FastMoney and RoundedMoney, the sibling module `jackson-datatype-moneta` can also be used.
179+
Refer to [javax-money-moneta](../javax-money-moneta/README.md) for more information.
180+
181+
### Custom Field Names
182+
183+
As you have seen in the previous examples the `JavaxMoneyModule` uses the field names `amount`, `currency` and `formatted`
184+
by default. Those names can be overridden if desired:
185+
186+
```java
187+
ObjectMapper mapper = JsonMapper.builder()
188+
.addModule(new JavaxMoneyModule()
189+
.withAmountFieldName("value")
190+
.withCurrencyFieldName("unit")
191+
.withFormattedFieldName("pretty"))
192+
.build();
193+
```
194+
195+
## Usage
196+
197+
After registering and configuring the module you're now free to directly use `MonetaryAmount` in your data types:
198+
199+
```java
200+
import javax.money.MonetaryAmount;
201+
202+
public class Product {
203+
private String sku;
204+
private MonetaryAmount price;
205+
...
206+
}
207+
```

0 commit comments

Comments
 (0)