REST服务器与Android应用间ObjectId转换不匹配问题
问题:Android中MongoDB ObjectId反序列化与数据库值不匹配
问题说明
从Spring Boot REST接口获取MongoDB文档数据时,Android端解析出的_id(ObjectId)与数据库中存储的十六进制值不一致,但其他字段(如content、createdAt等)解析正常。Postman中查看接口返回数据是正确的,排除请求方式问题,核心是ObjectId的反序列化逻辑出错。
MongoDB文档示例
{ "_id": { "$oid": "64a6d4ae3fe947597ff1fe23" }, "content": "my first post ", "createdAt": { "$date": "2023-07-06T14:50:22.778Z" }, "hashtags": [], "user": { "$ref": "app_users", "$id": { "$oid": "648da920b40b7222a0af8cb6" } }, "_class": "dev.social.media.security.model.Post" }
Android端相关代码
请求数据代码
// Make the API call to fetch the list of posts PostService postService = retrofit.create(PostService.class); Call<List<Post>> call = postService.getAllPosts(authToken); call.enqueue(new Callback<List<Post>>() { @Override public void onResponse(@NonNull Call<List<Post>> call, @NonNull Response<List<Post>> response) { System.out.println("we should get the postts here " + response.body()); System.out.println("header check " + response.headers()); System.out.println("Status code from server" + response.isSuccessful()); if (response.isSuccessful() && response.body() != null) { Gson gson = new Gson(); List<Post> posts = response.body(); String jsonResponse = gson.toJson(posts); System.out.println("Server response original: " + new Gson().toJson(posts)); System.out.println("Response body: " + jsonResponse); if (posts != null) { System.out.println("get id from the original post "+ posts.get(0).getIdWrapper()); System.out.println("get content from original post " + posts.get(0).getContent()); postAdapter.setPostList(posts); } else { System.out.println("Received null posts list"); } } else { System.out.println("Something went wrong displaying the post"); Toast.makeText(MainActivity.this, "Login Error", Toast.LENGTH_SHORT).show(); } } // 省略onFailure方法 });
Post模型类代码
public class Post { @SerializedName("_id") private ObjectId idWrapper; @SerializedName("content") private String content; @SerializedName("createdAt") private String createdAt; @SerializedName("updatedAt") private String updatedAt; @SerializedName("hashtags") private List<String> hashtags; @SerializedName("comments") private List<Comment> comments; @SerializedName("likes") private List<Like> likes; @SerializedName("user") private AppUser user; // empty constructor public Post() {} public Post(ObjectId idWrapper, String content, String createdAt, String updatedAt, List<String> hashtags, List<Comment> comments, List<Like> likes, AppUser user) { this.idWrapper = idWrapper; this.content = content; this.createdAt = createdAt; this.updatedAt = updatedAt; this.hashtags = hashtags; this.comments = comments; this.likes = likes; this.user = user; } public ObjectId getIdWrapper() { return idWrapper; } public void setIdWrapper(ObjectId idWrapper) { this.idWrapper = idWrapper; } // 省略其他getter/setter }
解决方案
问题根源:Gson默认无法识别MongoDB ObjectId的嵌套结构({"$oid": "xxx"}),直接用ObjectId类型接收会导致反序列化失败,生成错误的ID值。
方案1:自定义Gson TypeAdapter处理ObjectId
创建一个TypeAdapter来专门解析$oid结构:
public class ObjectIdTypeAdapter extends TypeAdapter<ObjectId> { @Override public void write(JsonWriter out, ObjectId value) throws IOException { if (value == null) { out.nullValue(); return; } out.beginObject(); out.name("$oid").value(value.toHexString()); out.endObject(); } @Override public ObjectId read(JsonReader in) throws IOException { if (in.peek() == JsonToken.NULL) { in.nextNull(); return null; } in.beginObject(); String oid = null; while (in.hasNext()) { if ("$oid".equals(in.nextName())) { oid = in.nextString(); } else { in.skipValue(); } } in.endObject(); return oid == null ? null : new ObjectId(oid); } }
然后在创建Retrofit时,使用注册了该Adapter的Gson实例:
Gson customGson = new GsonBuilder() .registerTypeAdapter(ObjectId.class, new ObjectIdTypeAdapter()) .create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create(customGson)) .build();
方案2:修改Post模型类直接接收ID字符串
将idWrapper改为String类型,并通过SerializedName直接映射_id.$oid字段:
public class Post { @SerializedName("_id.$oid") private String id; // 其他字段不变... public ObjectId getObjectId() { return new ObjectId(id); } // 省略其他方法 }
这种方式更简单,不需要自定义Adapter,后续如果需要ObjectId对象,再通过字符串转换即可。
方案3:调整Spring Boot服务器端序列化配置
在Spring Boot中配置MongoDB的序列化规则,直接将ObjectId序列化为字符串而非嵌套对象。例如使用Jackson的话,可以添加配置:
@Configuration public class MongoConfig { @Bean public MappingMongoConverter mappingMongoConverter(MongoDbFactory factory, MongoMappingContext context, BeanFactory beanFactory) { MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), context); converter.setTypeMapper(new DefaultMongoTypeMapper(null)); // 配置ObjectId序列化 converter.getConversionService().addConverter(ObjectId.class, String.class, ObjectId::toHexString); return converter; } }
这样服务器返回的_id就是直接的十六进制字符串,Android端模型类中直接用String或ObjectId接收即可。
内容的提问来源于stack exchange,提问作者ABAB
相关产品推荐
相关产品推荐

