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