1. 概述

在之前的文章中,我们已经了解过如何从 MongoDB 中将 BSON 文档映射为 Java 对象

这种方式在开发 REST 接口时非常常见,通常我们会先对这些对象进行一些逻辑处理,再通过 Jackson 等工具将其序列化为 JSON 返回给前端。

但有时候我们压根不想对数据做任何修改 —— 比如只是做一层简单的数据代理或快速导出。如果为此还去写一堆繁琐的 POJO 类和映射逻辑,就有点杀鸡用牛刀了。

解决方案:直接使用 BSON 到 JSON 的原生转换,跳过 Java 实体类这一层,简单粗暴又高效。

本文将带你掌握如何利用 MongoDB BSON API 实现高效的 BSON → JSON 转换,尤其解决开发中常见的“日期格式难看”这类踩坑问题。


2. 使用 Morphia 创建 BSON 文档

我们先用 Morphia 快速搭建环境(具体配置可参考 Morphia 使用指南),插入一条测试数据。

先定义一个 Book 实体类,涵盖常见字段类型:

@Entity("Books")
public class Book {
    @Id
    private String isbn;

    @Embedded
    private Publisher publisher;

    @Property("price")
    private double cost;

    @Property
    private LocalDateTime publishDate;

    // Getters and setters ...
}

接着初始化数据库并保存测试数据:

public class BsonToJsonLiveTest {

    private static final String DB_NAME = "library";
    private static Datastore datastore;

    @BeforeClass
    public static void setUp() {
        datastore = Morphia.createDatastore(MongoClients.create(), DB_NAME);
        datastore.getMapper().mapPackage("com.baeldung.bsontojson");
        datastore.ensureIndexes();

        datastore.save(new Book()
            .setIsbn("isbn")
            .setTitle("Java 编程思想")
            .setAuthor("Bruce Eckel")
            .setCost(99.00)
            .setPublisher(new Publisher(new ObjectId("fffffffffffffffffffffffa"),"机械工业出版社"))
            .setPublishDate(LocalDateTime.parse("2020-01-01T17:13:32Z", DateTimeFormatter.ISO_DATE_TIME))
            .addCompanionBooks(new Book().setIsbn("isbn2")));
    }
}

⚠️ 注意:Publisher 是嵌入式对象,cost 字段通过 @Property("price") 映射为 MongoDB 中的 price 字段,这些都是 Morphia 的常规操作,不再赘述。


3. 默认的 BSON 到 JSON 转换

MongoDB 的 Document 类原生支持 toJson() 方法,可以直接将 BSON 转为 JSON 字符串,用法极其简单:

@Test
public void givenBsonDocument_whenUsingStandardJsonTransformation_thenJsonDateIsObjectEpochTime() {

    String json;
    try (MongoClient mongoClient = MongoClients.create()) {
        MongoDatabase mongoDatabase = mongoClient.getDatabase(DB_NAME);
        Document bson = mongoDatabase.getCollection("Books").find().first();
        json = bson.toJson(JsonWriterSettings
                .builder()
                .dateTimeConverter(new JSONDateFormatEpochTime())
                .build());
    }

    String expectedJson = "{\"_id\": \"isbn\", " +
        "\"_t\": \"Book\", " +
        "\"title\": \"Java 编程思想\", " +
        "\"author\": \"Bruce Eckel\", " +
        "\"publisher\": {\"_id\": {\"$oid\": \"fffffffffffffffffffffffa\"}, " +
        "\"_t\": \"Publisher\", \"name\": \"机械工业出版社\"}, " +
        "\"price\": 99.0, " +
        "\"publish2Date\": {\"$date\": 1577898812000}}";

    assertNotNull(json);
    assertEquals(expectedJson, json);
}

输出结果如下:

{
    "_id": "isbn",
    "_t": "Book",
    "title": "Java 编程思想",
    "author": "Bruce Eckel",
    "publisher": {
        "_id": {
            "$oid": "fffffffffffffffffffffffa"
        },
        "_t": "Publisher",
        "name": "机械工业出版社"
    },
    "price": 99.0,
    "publishDate": {
        "$date": 1577898812000
    }
}

可以看到:

  • ✅ 所有字段正常输出
  • ❌ 日期字段 publishDate 被转成了 { "$date": 1577898812000 },即毫秒级 Unix 时间戳
  • ❌ ObjectId 被包装成 { "$oid": "..." } 结构

这种格式虽然标准,但前端 JavaScript 直接解析很不方便,尤其是 $date 这种非标准结构,需要额外处理。


3.1 自定义日期转换器:保留时间戳格式

如果你确实需要保留时间戳格式(比如对接某些老系统),可以自定义一个 Converter<Long> 来控制输出:

public class JSONDateFormatEpochTime implements Converter<Long> {

    @Override
    public void convert(Long value, StrictJsonWriter writer) {
        writer.writeStartObject();
        writer.writeName("$date");
        writer.writeNumber(String.valueOf(value));
        writer.writeEndObject();
    }
}

这个类的作用就是把 Long 类型的时间戳包装成 { "$date": 1234567890 } 的形式,和 MongoDB 的严格模式一致。


4. 使用宽松模式(Relaxed)转换日期

如果你希望日期显示为可读的 ISO 格式字符串,可以启用 Relaxed JSON 模式

bson.toJson(JsonWriterSettings
  .builder()
  .outputMode(JsonMode.RELAXED)
  .build());

输出结果变为:

{
    "_id": "isbn",
    "_t": "Book",
    "title": "Java 编程思想",
    "author": "Bruce Eckel",
    "publisher": {
        "_id": {
            "$oid": "fffffffffffffffffffffffa"
        },
        "_t": "Publisher",
        "name": "机械工业出版社"
    },
    "price": 99.0,
    "publishDate": {
        "$date": "2020-01-01T17:13:32Z"
    }
}

✅ 日期变成了 ISO 字符串
❌ 但依然包裹在 { "$date": "..." } 中,不够“干净”

前端仍需解析 $date 字段,体验不够丝滑。


5. 自定义转换器:输出纯 ISO 日期字符串

终极目标:让 publishDate 直接输出为 "2020-01-01T17:13:32Z",不带 $date 包装。

✅ 解决方案:实现 Converter<Long>,直接写入字符串。

public class JsonDateTimeConverter implements Converter<Long> {

    private static final Logger LOGGER = LoggerFactory.getLogger(JsonDateTimeConverter.class);
    static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_INSTANT
        .withZone(ZoneId.of("UTC"));

    @Override
    public void convert(Long value, StrictJsonWriter writer) {
        try {
            Instant instant = new Date(value).toInstant();
            String s = DATE_TIME_FORMATTER.format(instant);
            writer.writeString(s);
        } catch (Exception e) {
            LOGGER.error(String.format("Fail to convert offset %d to JSON date", value), e);
        }
    }
}

然后在 toJson 时注入该转换器:

bson.toJson(JsonWriterSettings
  .builder()
  .dateTimeConverter(new JsonDateTimeConverter())
  .build());

最终输出:

{
    "_id": "isbn",
    "_t": "Book",
    "title": "Java 编程思想",
    "author": "Bruce Eckel",
    "publisher": {
        "_id": {
            "$oid": "fffffffffffffffffffffffa"
        },
        "_t": "Publisher",
        "name": "机械工业出版社"
    },
    "price": 99.0,
    "publishDate": "2020-01-01T17:13:32Z"
}

✅ 完美!日期字段 now 是标准 JSON 字符串,前端可直接 new Date(json.publishDate) 解析。


6. 总结

本文带你实战了 MongoDB 中 BSON 到 JSON 的几种转换方式,重点解决开发中高频踩坑点 —— 日期格式不友好

核心要点总结:

方式 输出格式 适用场景
默认转换 { "$date": 1577898812000 } 严格兼容 MongoDB 协议
Relaxed 模式 { "$date": "2020-01-01T..." } 需要可读时间但接受包装
自定义 Converter "2020-01-01T..." 前端直用,推荐生产环境

最佳实践建议

  • 如果是内部服务间通信,可用 Relaxed 模式
  • 如果是对外 REST 接口,强烈建议使用自定义 Converter 输出纯字符串日期
  • 同样的套路也可用于 ObjectId、Decimal128 等类型的定制化输出

💡 小贴士:不要为了“省事”而返回带 $oid$date 的结构,那只会把麻烦转嫁给前端同事。

完整代码示例已上传至 GitHub:https://github.com/baeldung/tutorials/tree/master/persistence-modules/java-mongodb


原始标题:BSON to JSON Document Conversion in Java