在java项目中的mongodb的_id被fastjson转为json时竟然丢失了

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: fastjson是阿里开发的一个javaBean和json解析器和封装器(源码位置),用过几次感觉挺好用的,也是国人的开源项目当然得支持,但最近项目在使用mongodb作为数据库时出现了_id丢失的问题,现将我遇到的问题和解决办法展示一下。现将错误的程序代码添加上,然后再提供解决方法:package org.jivesoftware.openfire.plugin.f

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<FieldInfo> getters = TypeUtils.computeGetters(clazz, aliasMap, false);

调试结果:

[id, _id]

将三个属性的调试信息显示在下面:
1.属性id的调试信息,存在fieldInfoMap中
这里写图片描述

2.属性__id(双下划线)的调试信息
这里写图片描述

3.属性_id(单下划线)的调试信息
这里写图片描述

重点是id和_id,由上面的调试信息可以看到属性id的map信息是最先存入的,最后存入的_id,但两者的key都是id,因此_id就将id的map信息给覆盖掉了,因此,也就没有了属性id对应的map值。
fieldInfoMap只是暂存信息,需要返回给调用方法使用的信息存储在fieldInfoList 中,

  List<FieldInfo> fieldInfoList = new ArrayList<FieldInfo>();
  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);
      }
  }

执行的代码部分:
这里写图片描述
最后返回的fieldInfoList 截图如下:
这里写图片描述

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

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

1.不使用fastjson去解析,这是一种方法,但也是在逃避fastjson的问题。
2.将_id和id赋值相同(_id->面向mongodb,id->面向开发者),那么即使id属性被覆盖,但json中的值还是能获取到的,只不过是被相同的值替代。
3.使用fastjson注解方式实现:

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

fastjson提供了注解的方式可以为指定的属性设置要序列化的名称,需要配置在setter和getter方法上。
测试过的方式有三种:
(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]

因为序列化为json时属性名称还是错误的,但反序列化为java对象时是正确的。

关键代码如下:

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依然缺少一个下划线。

(4).终极解决,在属性的setter和getter方法上使用注解@JSONField(name="_id"),为其指定要序列化和反序列化后的属性名,getter是序列化为json,setter是反序列化为java对象。

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

还有其他撇开fastjson的方法:
(1).自己组串,不使用JavaBean,而直接写成json格式的字符串;
(2).改属性名,不使用类似”_id”和”id”这样的属性。
不知道为什么项目中会出现如此奇葩的定义,不过既然这样定义了,也使用了。还是做下记录比较好,也省的自己以后可能再做如此的重复工作。
(3).不为_id设置get方法,也可以获得正确的json串。但在解析为JavaBean对象时会报错。

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

相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
4天前
|
Java Android开发
Eclipse 创建 Java 项目
Eclipse 创建 Java 项目
21 4
|
10天前
|
SQL Java 数据库连接
从理论到实践:Hibernate与JPA在Java项目中的实际应用
本文介绍了Java持久层框架Hibernate和JPA的基本概念及其在具体项目中的应用。通过一个在线书店系统的实例,展示了如何使用@Entity注解定义实体类、通过Spring Data JPA定义仓库接口、在服务层调用方法进行数据库操作,以及使用JPQL编写自定义查询和管理事务。这些技术不仅简化了数据库操作,还显著提升了开发效率。
24 3
|
13天前
|
前端开发 Java 数据库
如何实现一个项目,小白做项目-java
本教程涵盖了从数据库到AJAX的多个知识点,并详细介绍了项目实现过程,包括静态页面分析、数据库创建、项目结构搭建、JSP转换及各层代码编写。最后,通过通用分页和优化Servlet来提升代码质量。
34 1
|
1月前
|
JavaScript 前端开发 Java
解决跨域问题大集合:vue-cli项目 和 java/springboot(6种方式) 两端解决(完美解决)
这篇文章详细介绍了如何在前端Vue项目和后端Spring Boot项目中通过多种方式解决跨域问题。
341 1
解决跨域问题大集合:vue-cli项目 和 java/springboot(6种方式) 两端解决(完美解决)
|
20天前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
26天前
|
NoSQL Java 数据库连接
MongoDB Java
10月更文挑战第18天
17 3
|
1月前
|
JSON JavaScript Java
在Java中处理JSON数据:Jackson与Gson库比较
本文介绍了JSON数据交换格式及其在Java中的应用,重点探讨了两个强大的JSON处理库——Jackson和Gson。文章详细讲解了Jackson库的核心功能,包括数据绑定、流式API和树模型,并通过示例演示了如何使用Jackson进行JSON解析和生成。最后,作者分享了一些实用的代码片段和使用技巧,帮助读者更好地理解和应用这些工具。
在Java中处理JSON数据:Jackson与Gson库比较
|
1月前
|
Java Apache Maven
Java/Spring项目的包开头为什么是com?
本文介绍了 Maven 项目的初始结构,并详细解释了 Java 包命名惯例中的域名反转规则。通过域名反转(如 `com.example`),可以确保包名的唯一性,避免命名冲突,提高代码的可读性和逻辑分层。文章还讨论了域名反转的好处,包括避免命名冲突、全球唯一性、提高代码可读性和逻辑分层。最后,作者提出了一个关于包名的问题,引发读者思考。
Java/Spring项目的包开头为什么是com?
|
1月前
|
运维 Java Maven
Dockerfile实践java项目
通过上述实践,我们可以看到,Dockerfile在Java项目中扮演着至关重要的角色,它不仅简化了部署流程,提高了环境一致性,还通过多阶段构建、环境变量配置、日志管理、健康检查等高级特性,进一步增强了应用的可维护性和可扩展性。掌握这些实践,将极大地提升开发和运维团队的工作效率。
48 1
|
1月前
|
算法 Java Linux
java制作海报五:java 后端整合 echarts 画出 折线图,项目放在linux上,echarts图上不显示中文,显示方框口口口
这篇文章介绍了如何在Java后端整合ECharts库来绘制折线图,并讨论了在Linux环境下ECharts图表中文显示问题。
39 1