java对象强制转换丢失id_在java项目中的mongodb的_id被fastjson转为json时竟然丢失了...

fastjson是阿里开发的一个javaBean和json解析器和封装器(源码位置),用过几次感觉挺好用的,也是国人的开源项目当然得支持,但最近项目在使用mongodb作为数据库时出现了_id丢失的问题,现将我遇到的问题和解决办法展示一下。

现将错误的程序代码添加上,然后再提供解决方法:

package org.jivesoftware.openfire.plugin.friends.test;

import org.bson.types.ObjectId;

import org.jivesoftware.openfire.plugin.friends.util.JsonUtil;

import com.alibaba.fastjson.annotation.JSONField;

/** *@author Administrator * */

public class TestJson {

private String id;

private String __id;

private ObjectId _id;

public String get__id() {

return __id;

}

public void set__id(String __id) {

this.__id = __id;

}

public String getId() {

return id;

}

public void setId(String id) {

this.id = id;

}

//@JSONField(serialize=false,deserialize=false)

public ObjectId get_id() {

return _id;

}

public void set_id(ObjectId _id) {

this._id = _id;

}

@Override

public String toString() {

return "TestJson [id=" + id + ", __id=" + __id + ", _id=" + _id + "]";

}

public static void main(String[] args) throws Exception {

TestJson testJson = new TestJson();

testJson.setId("1234567890987654");

testJson.set__id("abcdefghijklmnopqrst");

String json = JSON.toJSONString(testJson);

System.out.println("json:"+json);

TestJson tj =(TestJson)JSON.parseObject(json,TestJson.class);

System.out.println(tj.toString());

}

}

当然要先把fastjson和mongodb的jar包导入项目。

执行后的结果如下:

json:{"_id":"abcdefghijklmnopqrst"}

TestJson [id=null, __id=abcdefghijklmnopqrst, _id=null]

有没有发现属性id的值没有被转为JSON?

这是在项目开发中困扰了有一段时间的问题,因为开发者需要将id作为参数传递给服务器做删改查操作。而没有了id,我怎么去操作。工作一时陷入停顿。

没办法,在网上没找到解决办法,就只能去调试跟踪fastjson源码,最终发现问题所在,贴出问题出现的fastjson的源码片段:

//代码片段1

char c3 = methodName.charAt(3);

String propertyName;

if (Character.isUpperCase(c3)) {

if (compatibleWithJavaBean) {

propertyName = decapitalize(methodName.substring(3));

} else {

propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);

}

} else if (c3 == '_') {

propertyName = methodName.substring(4);

} else if (c3 == 'f') {

propertyName = methodName.substring(3);

} else if (methodName.length()>=5 && Character.isUpperCase(methodName.charAt(4))){

propertyName = decapitalize(methodName.substring(3));

} else {

continue;

}

该代码片段位置:src/main/java/com/alibaba/fastjson/util/TypeUtils.java中。

现在解释下我跟踪到的根源:

看这段代码

else if (c3 == '_') {

propertyName = methodName.substring(4);

}

这句话就是导致_id丢失或者说id属性值丢失的原因所在,fastjson获得JavaBean对象属性作为json中key的方式是通过截取JavaBean中属性的getter方法名得到的,而c3是获取的方法名的index=3,也就是第四个字母。而因为我的getter和setter方法都是通过Eclipse快捷生成的,因此getter方法名get_id的第四个位置是下划线,而当该位置是下划线时,默认获取的属性名(也就是_id转为json中的key)是从下一个位置开始的,最终propertyName=”id”.

追踪源码:

List getters = TypeUtils.computeGetters(clazz, aliasMap, false);

调试结果:

[id, _id]

将三个属性的调试信息显示在下面:

1.属性id的调试信息,存在fieldInfoMap中

0818b9ca8b590ca3270a3433284dd417.png

2.属性__id(双下划线)的调试信息

0818b9ca8b590ca3270a3433284dd417.png

3.属性_id(单下划线)的调试信息

0818b9ca8b590ca3270a3433284dd417.png

重点是id和_id,由上面的调试信息可以看到属性id的map信息是最先存入的,最后存入的_id,但两者的key都是id,因此_id就将id的map信息给覆盖掉了,因此,也就没有了属性id对应的map值。

fieldInfoMap只是暂存信息,需要返回给调用方法使用的信息存储在fieldInfoList 中,

List fieldInfoList = new ArrayList();

if (containsAll) {

for (String item : orders) {

FieldInfo fieldInfo = fieldInfoMap.get(item);

fieldInfoList.add(fieldInfo);

}

} else {

for (FieldInfo fieldInfo : fieldInfoMap.values()) {

fieldInfoList.add(fieldInfo);

}

if (sorted) {

Collections.sort(fieldInfoList);

}

}

执行的代码部分:

0818b9ca8b590ca3270a3433284dd417.png

最后返回的fieldInfoList 截图如下:

0818b9ca8b590ca3270a3433284dd417.png

到了这里应该就大体明白了,fastjson是通过截取get方法的方式来获取属性名的,而在一般情况下,getter和setter方法通过Eclipse快捷生成,并且在属性中没有下划线开头的属性的时候,代码片段1处的方法执行是没有错误的,但错就错在如果属性以下划线开头,那么生成的json中key和JavaBean中属性就不能对应了,很容易造成本例中的误解,误认为是__id(双下划线),没有解析成功。

既然错误的根源找到了,那我们就需要提供一些办法来解决掉这些问题。

1.不使用fastjson去解析,这是一种方法,但也是在逃避fastjson的问题。

2.将_id和id赋值相同(_id->面向mongodb,id->面向开发者),那么即使id属性被覆盖,但json中的值还是能获取到的,只不过是被相同的值替代。

3.使用fastjson注解方式实现:前提是_id属性不需要出现在json中。

这里重点说一下第三种方法:

fastjson提供了注解的方式可以将不需要的对象属性忽略掉,本例中也就是需要将_id忽略掉才能使得属性id的值正常显示。

测试过的方式有三种:

(1).在类名称上添加忽略对象注解,如@JSONType(ignores="_id")。是不是觉得这样就能在解析为json时将属性_id的影响消除?接着往下看

boolean ignore = isJSONTypeIgnore(clazz, propertyName);

if (ignore) {

continue;

}

这句代码是在获得属性propertyName之后,然后判断与注解中忽略的_id是否匹配,如果ignore=false;则将属性加入到fieldInfoMap中,否则直接进行下一次循环。

返回值就如这样:

json:{}

TestJson [id=null, __id=null, _id=null]

因为在构建fieldInfoMap时,__id(双下划线)对应的_id未被加入到fieldInfoMap中,而_id对应的id的值为空,因此生成的json就只是空对象。因此该注解也不能很好的解决该问题。

(2).在属性_id上添加注解,如@JSONField(serialize=false,deserialize=false)

@JSONField(serialize=false,deserialize=false)

private ObjectId _id;

貌似根据以前使用注解,这样子定义在属性上的不可被序列化和反序列化的注解应该可以正常使用了,但本例子又跟您开了个玩笑,即结果跟方法1的一样。

json:{}

TestJson [id=null, __id=null, _id=null]

原因就出现下面的代码中:

Field field = ParserConfig.getField(clazz, propertyName);

JSONField fieldAnnotation = field.getAnnotation(JSONField.class);

if (fieldAnnotation != null) {

if (!fieldAnnotation.serialize()) {

continue;

}

该代码也是在解析了属性名称之后去被获取注解的属性,因此也就会造成该注解对应的属性比实际的属性少一个下划线”_”(如果属性确实以下划线开始),那就会造成开发中被注解的属性依然序列化,而未被注解的属性却未被序列化。

要么这就是阿里在开发该解析器时对应用场景考虑不周,要么就是阿里开发人员默认认为使用者不会定义以下划线开头的属性(不过要真是这个原因,那我也无话可说了)。

(3).在属性_id的get方法上添加注解,如@JSONField(serialize=false,deserialize=false),该注解和方法2中的注解相同,不同之处就是注释的位置不同而已。

@JSONField(serialize=false,deserialize=false)

public ObjectId get_id() {

return _id;

}

这样得出的结果就是正确的:

json:{"_id":"abcdefghijklmnopqrst","id":"1234567890987654"}

TestJson [id=1234567890987654, __id=abcdefghijklmnopqrst, _id=null]

关键代码如下:

JSONField annotation = method.getAnnotation(JSONField.class);

if (annotation == null) { annotation = getSupperMethodAnnotation(clazz, method); }

if (annotation != null) { if (!annotation.serialize()) { continue; }

...

}

这句话是检测方法上的JSONField注解,如果有该注解,并且annotation.serialize()==false。则认为该方法及其对应的属性不被序列化,因此就执行continue,进入下一次循环,也就避免了id的map被_id的map覆盖的悲剧。同时也就能正确的将有非空值的属性转为json对象。虽然结果是正确的,但JavaBean中以下划线开头的属性对应的json中的key依然缺少一个下划线。

以上是使用fastjson解决该问题,暂时得出结论是像类似同一JavaBean中有”_id”和”id”这样的属性,就只能使用在get方法上添加JSONField注解的方式解决。

还有其他撇开fastjson的方法:

(1).自己组串,不使用JavaBean,而直接写成json格式的字符串;

(2).改属性名,不使用类似”_id”和”id”这样的属性。

不知道为什么项目中会出现如此奇葩的定义,不过既然这样定义了,也使用了。还是做下记录比较好,也省的自己以后可能再做如此的重复工作。

(3).不为_id设置get方法,也可以获得正确的json串。但在解析为JavaBean对象时会报错。

心得:发现在使用开源框架进行开发时,如果网上没有很好的解决办法,跟踪阅读程序源码是个一举多得的好办法,即能深入了解问题根源,又能学习优秀源码,还能提高自己处理问题的能力。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要将JSON文档数组插入MongoDB,可以使用MongoDBJava驱动程序和BSON库来实现。 下面是一个简单的示例代码,演示如何将JSON文档数组插入MongoDB。 ```java import com.mongodb.MongoClient; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; import org.bson.Document; import org.json.JSONArray; import org.json.JSONObject; public class InsertJsonArrayToMongoDB { public static void main(String[] args) { // 创建MongoDB客户端 MongoClient mongoClient = new MongoClient("localhost", 27017); // 获取要使用的数据库 MongoDatabase db = mongoClient.getDatabase("test"); // 获取要使用的集合 MongoCollection<Document> collection = db.getCollection("mycollection"); // 创建JSON文档数组 JSONArray jsonArray = new JSONArray(); JSONObject json1 = new JSONObject(); json1.put("name", "John"); json1.put("age", 30); jsonArray.put(json1); JSONObject json2 = new JSONObject(); json2.put("name", "Mary"); json2.put("age", 25); jsonArray.put(json2); // 将JSON文档数组转换为BSON文档数组 Document[] docs = new Document[jsonArray.length()]; for (int i = 0; i < jsonArray.length(); i++) { JSONObject json = jsonArray.getJSONObject(i); Document doc = Document.parse(json.toString()); docs[i] = doc; } // 将BSON文档数组插入MongoDB collection.insertMany(Arrays.asList(docs)); // 关闭MongoDB客户端 mongoClient.close(); } } ``` 需要注意的是,上述代码JSON文档数组转换为BSON文档数组,使用了`Document.parse(json.toString())`方法。这是因为MongoDBJava驱动程序不支持直接将JSONObject对象转换为BSON文档对象,需要先将JSONObject对象转换JSON字符串,再使用`Document.parse()`方法将JSON字符串转换为BSON文档对象。 如果在插入JSON文档数组出现错误,可以检查JSON文档的格式是否正确,以及转换为BSON文档是否有误。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值