You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot如何优雅序列化MonetaryAmount仅展示金额与币种

问题描述

在Spring Boot+MongoDB项目中,使用org.javamoney.moneta的MonetaryAmount类型存储商品价格。当前API返回的JSON中,price字段序列化后包含大量冗余内部属性(如context、factory等),希望仅展示金额(amount)和币种(currency)。

已配置Mongo自定义转换器(MoneyReadConverter、MoneyWriteConverter)解决了数据库存储/读取的问题,但API接口的JSON序列化问题未解决;尝试引入Jackson相关库时出现配置冲突,导致应用崩溃。需要通过注解方式实现该字段的自定义序列化与反序列化。

相关代码如下:

ProductDto

@Data
@Builder
public class ProductDto {
    @Id
    private String id;
    private String name;
    private String description;
    private Category category;
    private List<Variation> variations;
    private MonetaryAmount price;
    private Double vat;
    private boolean active;
}

MongoDB配置

@Configuration
public class MongoConfiguration {
    @Bean
    public MongoCustomConversions mongoCustomConversions() {
        return new MongoCustomConversions( List.of( new MoneyReadConverter(), new MoneyWriteConverter() ) );
    }
}

MoneyReadConverter

package com.www.converter;

import javax.money.MonetaryAmount;
import org.javamoney.moneta.Money;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;

@ReadingConverter
public class MoneyReadConverter implements Converter<String, MonetaryAmount> {
    @Override
    public MonetaryAmount convert(String moneyString) {
        return Money.parse(moneyString);
    }
}

MoneyWriteConverter

package com.www.converter;

import javax.money.MonetaryAmount;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;

@WritingConverter
public class MoneyWriteConverter implements Converter<MonetaryAmount, String> {
    @Override
    public String convert(MonetaryAmount money) {
        return money.toString();
    }
}

Controller

@GetMapping
Flux<CatalogDto> getCatalogs() {
    return catalogService.getAll();
}

Service

@Override
public Flux<CatalogDto> getAll() {
    Flux<Catalog> catalogFlux  = catalogRepository.findAll();
    return catalogFlux
            .map( CatalogMapper::mapToCatalogDto )
            .switchIfEmpty( Flux.empty());
}

依赖

<dependency>
    <groupId>org.javamoney</groupId>
    <artifactId>moneta</artifactId>
    <version>1.1</version>
</dependency>
解决方案

无需引入额外Jackson扩展库,通过自定义序列化器/反序列化器结合字段注解实现需求,避免配置冲突。

1. 自定义Jackson序列化器

创建MonetaryAmountSerializer,将MonetaryAmount序列化为仅包含amount和currency的JSON结构:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import javax.money.MonetaryAmount;
import java.io.IOException;

public class MonetaryAmountSerializer extends JsonSerializer<MonetaryAmount> {
    @Override
    public void serialize(MonetaryAmount monetaryAmount, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeNumberField("amount", monetaryAmount.getNumber().doubleValue());
        jsonGenerator.writeStringField("currency", monetaryAmount.getCurrency().getCurrencyCode());
        jsonGenerator.writeEndObject();
    }
}

2. 自定义Jackson反序列化器

创建MonetaryAmountDeserializer,解析前端传入的精简JSON结构为MonetaryAmount对象:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import org.javamoney.moneta.Money;
import javax.money.MonetaryAmount;
import java.io.IOException;

public class MonetaryAmountDeserializer extends JsonDeserializer<MonetaryAmount> {
    @Override
    public MonetaryAmount deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        double amount = node.get("amount").asDouble();
        String currencyCode = node.get("currency").asText();
        return Money.of(amount, currencyCode);
    }
}

3. 给DTO字段添加注解

修改ProductDto的price字段,指定自定义序列化器和反序列化器:

@Data
@Builder
public class ProductDto {
    @Id
    private String id;
    private String name;
    private String description;
    private Category category;
    private List<Variation> variations;
    
    @JsonSerialize(using = MonetaryAmountSerializer.class)
    @JsonDeserialize(using = MonetaryAmountDeserializer.class)
    private MonetaryAmount price;
    
    private Double vat;
    private boolean active;
}

4. 关键注意事项

  • 不要引入jackson-datatype-money等第三方Jackson扩展,避免和Spring Boot默认Jackson配置冲突;
  • 确保moneta依赖版本与Spring Boot版本兼容,若出现依赖冲突,可通过<exclusions>排除冲突的子依赖;
  • 若使用MapStruct等映射工具,MonetaryAmount类型可直接在实体与DTO间映射,无需额外配置(Mongo转换器负责数据库与实体的转换,Jackson注解负责API序列化)。

内容的提问来源于stack exchange,提问作者cyril

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 01:00:41