Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ datatype modules to support 3rd party libraries.
Currently included are:

* [jackson-datatype-joda-money](joda-money/) for [Joda-Money](https://www.joda.org/joda-money/) datatypes
* [jackson-datatype-money](javax-money/) for [JavaMoney](https://javamoney.github.io/) datatypes (starting with Jackson 2.19)
* 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`)
Expand All @@ -16,6 +17,7 @@ Currently included are:
Note that this repo was created for Jackson 2.11: prior to this, individual datatype
modules had their own repositories.


## License

All modules are licensed under [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt).
Expand Down Expand Up @@ -62,6 +64,7 @@ mapper.registerModule(new JSONPModule()); // new (jakarta) json-P API
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JsonOrgModule())
.addModule(new JodaMoneyModule())
.addModule(new MoneyModule())
// ONE of these (not both):
.addModule(new JSR353Module()) // old (javax) json-p API
.addModule(new JSONPModule()) // new (jakarta) json-P API
Expand Down
212 changes: 212 additions & 0 deletions javax-money/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# Jackson Datatype Money

*Jackson Datatype Money* is a [Jackson](https://github.com/codehaus/jackson) module to support JSON serialization and
deserialization of [JavaMoney](https://github.com/JavaMoney/jsr354-api) data types. It fills a niche, in that it
integrates JavaMoney 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.

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
- JavaMoney

## Installation

Add the following dependency to your project:

```xml

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

For ultimate flexibility, this module is compatible with the official version as well as the backport of JavaMoney. The
actual version will be selected by a profile based on the current JDK version.

## Configuration

Register the module with your `ObjectMapper`:

```java
ObjectMapper mapper = new ObjectMapper()
.registerModule(new MoneyModule());
Copy link
Member

Choose a reason for hiding this comment

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

Let's change to use new-er "addModule":

ObjectMapper mapper = JsonMapper.builder()
  .addModule(...)
  .build();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated.

```

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 = new ObjectMapper()
.registerModule(new MoneyModule().withQuotedDecimalNumbers());
Copy link
Member

Choose a reason for hiding this comment

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

same here (addModule())

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated.

```

```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 = new ObjectMapper()
.registerModule(new MoneyModule().withDefaultFormatting());
```

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

```java
ObjectMapper mapper = new ObjectMapper()
.registerModule(new MoneyModule()
.withFormatting(new CustomMonetaryAmountFormatFactory()));
```

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 use `org.javamoney.moneta.Money` as an implementation for `javax.money.MonetaryAmount` by default when
deserializing money values. If you need a different implementation, you can pass a different `MonetaryAmountFactory`
to the `MoneyModule`:

```java
ObjectMapper mapper = new ObjectMapper()
.registerModule(new MoneyModule()
.withMonetaryAmount(new CustomMonetaryAmountFactory()));
```

You can also pass in a method reference:

```java
ObjectMapper mapper = new ObjectMapper()
.registerModule(new MoneyModule()
.withMonetaryAmount(FastMoney::of));
```

*Jackson Datatype Money* comes with support for all `MonetaryAmount` implementations from Moneta, the reference
implementation of JavaMoney:

| `MonetaryAmount` Implementation | Factory |
|-------------------------------------|-----------------------------------------------------------------------------------------------------------------------|
| `org.javamoney.moneta.FastMoney` | [`new MoneyModule().withFastMoney()`](src/main/java/com/fasterxml/jackson/datatype/money/FastMoneyFactory.java) |
| `org.javamoney.moneta.Money` | [`new MoneyModule().withMoney()`](src/main/java/com/fasterxml/jackson/datatype/money/MoneyFactory.java) |
| `org.javamoney.moneta.RoundedMoney` | [`new MoneyModule().withRoundedMoney()`](src/main/java/com/fasterxml/jackson/datatype/money/RoundedMoneyFactory.java) | |

Module supports deserialization of amount number from JSON number as well as from JSON string without any special
configuration required.

### Custom Field Names

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

```java
ObjectMapper mapper = new ObjectMapper()
.registerModule(new MoneyModule()
.withAmountFieldName("value")
.withCurrencyFieldName("unit")
.withFormattedFieldName("pretty"));
```

## 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;
...
}
```
124 changes: 124 additions & 0 deletions javax-money/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- This module was also published with a richer model, Gradle metadata, -->
<!-- which should be used instead. Do not delete the following line which -->
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
<!-- that they should prefer consuming it instead. -->
<!-- do_not_remove: published-with-gradle-metadata -->
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatypes-misc-parent</artifactId>
<version>2.19.0-SNAPSHOT</version>
</parent>
<artifactId>jackson-datatype-money</artifactId>
<name>Jackson datatype: javax-money</name>
<packaging>jar</packaging>
<version>2.19.0-SNAPSHOT</version>
<description>Support for datatypes of Javax Money library (https://javamoney.github.io/)
</description>
<url>https://github.com/FasterXML/jackson-datatypes-misc</url>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<properties>
<!-- Generate PackageVersion.java into this directory. -->
<packageVersion.dir>com/fasterxml/jackson/datatype/money</packageVersion.dir>
<packageVersion.package>${project.groupId}.money</packageVersion.package>
<slf4j.version>2.0.6</slf4j.version>
<junit-jupiter.version>5.9.2</junit-jupiter.version>
</properties>

<dependencies>
<dependency>
<groupId>javax.money</groupId>
<artifactId>money-api</artifactId>
<version>1.1</version>
</dependency>

<dependency>
<groupId>org.javamoney.moneta</groupId>
<artifactId>moneta-core</artifactId>
Copy link
Member

Choose a reason for hiding this comment

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

Is this test-only dependency? Or does module require specific Money API implementation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The module supports Money API implementations like moneta's FastMoney, Money and RoundedMoney.

Copy link
Member

Choose a reason for hiding this comment

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

Right: I was wondering if inclusion of specific implementation was problematic wrt using something else -- but I guess one would just use Maven dependency exclude, or some other mechanism.

Or put another way: I can see such dependency necessary for testing but wasn't sure it was really needed as regular ("compile") dependency. If you think it is needed that's fine: just double-checking.

Copy link

@KeepItSimpleStupid KeepItSimpleStupid Jan 14, 2025

Choose a reason for hiding this comment

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

Hi !
First, thanks for this jackson-datatype-money module to the Zalando team, we're using it for a long time and are really pleased that it's about to be more integrated to the Jackson ecosystem :)

Specifically on this thread :
I would say that if the module is about to be named javax-money, maybe it's worth reconsidering the support of the deserialization of org.javamoney.moneta.FastMoney, org.javamoney.moneta.Money, org.javamoney.moneta.RoundedMoney, 3 concrete classes belonging to Moneta, the only implementation of javax-money spec and support only the deserialization of javax.money.MonetaryAmount ?
Except for tests, the usage of the package org.javamoney is only located in MoneyModule.java so it seems quite straightforward to remove the support and get rid of moneta-core as a compile time dependency.
For us who use only javax.money.MonetaryAmount in our code, it would ease the dependency management since we won't have to fear to break this module if we update Moneta.
Maybe that would mean an additional module javax-money-moneta if deserializing those 3 classes is still needed by some ? This module could depend and reuse some code from the javax-money module though

Copy link
Member

Choose a reason for hiding this comment

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

@KeepItSimpleStupid Sounds like a good idea to me; modules should focus on doing one concrete thing well and just that (as general guideline).

Copy link
Member

Choose a reason for hiding this comment

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

If there was an CCLA, yes, but I haven't gotten one from Zalando. And based on discussions it sounded like there was hesitation to go with CLA. Either one is fine with me, but I do want this first inclusion to be covered by one or the other.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Based on this comment, I think we will make a CCLA following the merge.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bocytko Is my assumption correct?

Copy link

Choose a reason for hiding this comment

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

@cowtowncoder there is no hesitation on the CLA. Based on prior comments I thought that there is no dependency between the PR merge and CLA. All that matters is that abaf36d and 487cbf9 covering the OSS contribution do not require the CLA. Fine for the follow-on commits which accumulated in this PR :)

Time-wise, if bound to a printout+signature, I can get the CCLA signed and sent to you early next week.

Copy link
Member

@cowtowncoder cowtowncoder Feb 12, 2025

Choose a reason for hiding this comment

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

Ok I may have been unclear here. I think every PR should be done under a CLA or CCLA -- but for the initial one I'd only need one that covers the submitter (and not everyone who collaborated on source repository).
And then from thereon the usual (C)CLA coverage for further PRs (that is, if there are new contributors not yet covered).

Timing is fine with me -- I will make sure this gets merge before 2.19.0 gets released.
I will be taking one week off in a week so there's no super hurry.

I hope this helps!

<version>1.4.2</version>
</dependency>

<dependency>
<groupId>org.apiguardian</groupId>
<artifactId>apiguardian-api</artifactId>
<version>1.1.2</version>
Copy link
Member

Choose a reason for hiding this comment

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

Also, this... if these are annotations, should probably use scope of provided?

At least it would seem like this would not be typical compile dependency for actual functionality?

</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>

<!-- test -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.5.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jsonSchema</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kjetland</groupId>
<artifactId>mbknor-jackson-jsonschema_2.12</artifactId>
<version>1.0.39</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>pl.pragmatists</groupId>
<artifactId>JUnitParams</artifactId>
<version>1.1.1</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
</plugin>
<plugin>
<groupId>org.moditect</groupId>
<artifactId>moditect-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.fasterxml.jackson.datatype.money;

import org.apiguardian.api.API;

import javax.money.MonetaryAmount;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

@API(status = EXPERIMENTAL)
public interface AmountWriter<T> {

Class<T> getType();

T write(MonetaryAmount amount);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.fasterxml.jackson.datatype.money;

import org.apiguardian.api.API;

import java.math.BigDecimal;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

@API(status = EXPERIMENTAL)
public interface BigDecimalAmountWriter extends AmountWriter<BigDecimal> {

@Override
default Class<BigDecimal> getType() {
return BigDecimal.class;
}

}
Loading
Loading