Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 655|回复: 0

关于json-lib中日期类型转换的分析与问题解决

[复制链接]
  • TA的每日心情
    奋斗
    昨天 22:25
  • 签到天数: 790 天

    [LV.10]以坛为家III

    2049

    主题

    2107

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    722766
    发表于 2021-6-28 14:09:12 | 显示全部楼层 |阅读模式

    说明:本文中的json-lib版本为

    <dependency>
        <groupId>net.sf.json-lib</groupId>
        <artifactId>json-lib</artifactId>
        <version>2.4</version>
        <classifier>jdk15</classifier>
    </dependency>

    json-lib提供了将java对象与json字符串相互转换的能力,主要覆盖所有java基本类型与java基本类型的包装型。

    相关代码如下:

    net.sf.json.util.JSONUtils

       private static final MorpherRegistry morpherRegistry = new MorpherRegistry();
    
       static{
          // register standard morphers
          MorphUtils.registerStandardMorphers( morpherRegistry );
       }

    net.sf.ezmorph.MorphUtils

       /**
        * Clears and registers all standard morpehrs.
        *
        * @param morpherRegistry
        */
       public static void registerStandardMorphers( MorpherRegistry morpherRegistry )
       {
          morpherRegistry.clear();
          registerStandardPrimitiveMorphers( morpherRegistry );
          registerStandardPrimitiveArrayMorphers( morpherRegistry );
          registerStandardObjectMorphers( morpherRegistry );
          registerStandardObjectArrayMorphers( morpherRegistry );
       }
       public static void registerStandardPrimitiveMorphers( MorpherRegistry morpherRegistry )
       {
          morpherRegistry.registerMorpher( new BooleanMorpher( false ) );
          morpherRegistry.registerMorpher( new CharMorpher( '\0' ) );
          morpherRegistry.registerMorpher( new ByteMorpher( (byte) 0 ) );
          morpherRegistry.registerMorpher( new ShortMorpher( (short) 0 ) );
          morpherRegistry.registerMorpher( new IntMorpher( 0 ) );
          morpherRegistry.registerMorpher( new LongMorpher( 0 ) );
          morpherRegistry.registerMorpher( new FloatMorpher( 0 ) );
          morpherRegistry.registerMorpher( new DoubleMorpher( 0 ) );
       }
       public static void registerStandardObjectMorphers( MorpherRegistry morpherRegistry )
       {
          morpherRegistry.registerMorpher( new BooleanObjectMorpher( Boolean.FALSE ) );
          morpherRegistry.registerMorpher( new CharacterObjectMorpher( new Character( '\0' ) ) );
          morpherRegistry.registerMorpher( StringMorpher.getInstance() );
          morpherRegistry.registerMorpher( new NumberMorpher( Byte.class, new Byte( (byte) 0 ) ) );
          morpherRegistry.registerMorpher( new NumberMorpher( Short.class, new Short( (short) 0 ) ) );
          morpherRegistry.registerMorpher( new NumberMorpher( Integer.class, new Integer( 0 ) ) );
          morpherRegistry.registerMorpher( new NumberMorpher( Long.class, new Long( 0 ) ) );
          morpherRegistry.registerMorpher( new NumberMorpher( Float.class, new Float( 0 ) ) );
          morpherRegistry.registerMorpher( new NumberMorpher( Double.class, new Double( 0 ) ) );
          morpherRegistry.registerMorpher( new NumberMorpher( BigInteger.class, BigInteger.ZERO ) );
          morpherRegistry.registerMorpher( new NumberMorpher( BigDecimal.class,
                MorphUtils.BIGDECIMAL_ZERO ) );
          morpherRegistry.registerMorpher( ClassMorpher.getInstance() );
       }

    主要涉及的类型都是在这里注册的,大家可以看到没有我们常见的日期型,json-lib本身并没有提供对日期型的支持,对于它来说日期型只是一般Object,在处理的时候都是当成一般对象来处理的,比如在java转json的时候,会生成如下的json

    java对象:

    import java.util.Date;
    
    public class JSONTestEntity {
        private Date aa;
    private Timestamp bb;
    private java.sql.Date cc;

    转换的json

    {"aa":{"date":9,"day":4,"hours":14,"minutes":56,"month":10,"seconds":44,"time":1510210604836,"timezoneOffset":-480,"year":117},
    "bb":{"date":9,"day":4,"hours":14,"minutes":56,"month":10,"nanos":836000000,"seconds":44,"time":1510210604836,"timezoneOffset":-480,"year":117},"cc":null,"ss":"{:\"'},"}

    可以看到aa被当成了一个对象来解析了,date的属性被反射了出来。同理timeStamp也是可以转化的。

    util.Date还算是凑巧能解析出来,而其sql.Date就没这么幸运了。

    sql.Date在转换的时候会报如下错误:

    Caused by: java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.apache.commons.beanutils.PropertyUtilsBean.invokeMethod(PropertyUtilsBean.java:2155)
        at org.apache.commons.beanutils.PropertyUtilsBean.getSimpleProperty(PropertyUtilsBean.java:1323)
        at org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(PropertyUtilsBean.java:762)
        at org.apache.commons.beanutils.PropertyUtilsBean.getProperty(PropertyUtilsBean.java:837)
        at org.apache.commons.beanutils.PropertyUtils.getProperty(PropertyUtils.java:426)
        at net.sf.json.JSONObject.defaultBeanProcessing(JSONObject.java:749)
        ... 13 more
    Caused by: java.lang.IllegalArgumentException
        at java.sql.Date.getHours(Date.java:143)

    原因是sql.Date重写了util.Date的getHours方法

    package java.sql;
    
    public class Date extends java.util.Date {
    
       /**
        * This method is deprecated and should not be used because SQL Date 
        * values do not have a time component.
        *
        * @deprecated
        * @exception java.lang.IllegalArgumentException if this method is invoked
        * @see #setHours
        */
        public int getHours() {
        throw new java.lang.IllegalArgumentException();
        }

    导致json-lib在执行反射的时候直接抛了这个异常。

    所以这里我们也能看出在java->json的时候主要是用反射去取属性值,再执行相应的get方法来获得。

    上面是java->json时候的问题,那么接下来,json->java能不能顺利进行呢?

    我们将上面的生成的字符串回解析成java,结果发现报了如下错误:

    Exception in thread "main" net.sf.json.JSONException: java.lang.NoSuchMethodException: java.sql.Timestamp.<init>()
        at net.sf.json.JSONObject.toBean(JSONObject.java:288)
        at net.sf.json.JSONObject.toBean(JSONObject.java:406)
        at net.sf.json.JSONObject.toBean(JSONObject.java:233)
        at com.aisino.wsbs.utils.JsonUtils.stringToJavaBean(JsonUtils.java:46)
        at test.aisino.wsbs.utils.TestJsonValue.main(TestJsonValue.java:30)
    Caused by: java.lang.NoSuchMethodException: java.sql.Timestamp.<init>()
        at java.lang.Class.getConstructor0(Class.java:2706)
        at java.lang.Class.getDeclaredConstructor(Class.java:1985)
        at net.sf.json.util.NewBeanInstanceStrategy$DefaultNewBeanInstanceStrategy.newInstance(NewBeanInstanceStrategy.java:55)
        at net.sf.json.JSONObject.toBean(JSONObject.java:282)
        ... 4 more

    为什么呢?因为json-lib在将json转化成java对象的时候,需要实例化对象,它实例化对象是调用newInstance。而源码里它只会去取java对象的无参构造,毕竟它不知道你的java对象定义了几个构造函数,每个构造函数又传几个参数。

       private static final class DefaultNewBeanInstanceStrategy extends NewBeanInstanceStrategy {
          private static final Object[] EMPTY_ARGS = new Object[0];
          private static final Class[] EMPTY_PARAM_TYPES = new Class[0];
    
          public Object newInstance( Class target, JSONObject source ) throws InstantiationException,
                IllegalAccessException, SecurityException, NoSuchMethodException,
                InvocationTargetException {
             if( target != null ){
                Constructor c = target.getDeclaredConstructor( EMPTY_PARAM_TYPES );
                c.setAccessible( true );
                try {
                   return c.newInstance( EMPTY_ARGS );
                } catch ( InstantiationException e ) {
                   // getCause() was added on jdk 1.4

    所以小伙伴们注意了:在用json-lib转java对象的时候,至少要有一个无参构造函数,或者不写任何构造函数。

    而我们的TimeStamp恰巧不巧,没有!!!!

    所以,毫无疑问的又报错了。

    同样sql.Date也没有无参构造

     

    如果去掉这个timestamp和sql.Date,只保留util.Date,那么会发现可以成功

    好了,看上去只有util.Date可以正常的走完整个流程,但是这里还有一个坑:在json转java的过程中,如果对应的属性没有值会发生什么呢。

    我们构造一个测试的string:

    static final String testJsonStr = "{\"aa\":\"\",\"bb\":\"\",\"cc\":\"\",\"ss\":\"{:\\\"'},\"}";
    
    JSONTestEntity jsonObject2 = JsonUtils.stringToJavaBean(testJsonStr, JSONTestEntity.class);
            System.out.println(jsonObject2);

    得到的结果如下:

    JSONTestEntity [aa=Thu Nov 09 15:22:31 CST 2017, bb=, cc=null, ss={:"'},]

     我们可以看到aa里面被赋上的当前时间,原因很简单,因为在初始化java对象Date的时候,newInstance等于是new Date(),它就是系统当前时间,而又没有任何值进行反射,所以就是当前时间。

    注意,我们这里构造的json给aa的值是“”,如果给的null,则不会初始化

    static final String testJsonStr = "{\"aa\":null,\"cc\":null,\"ss\":\"{:\\\"'},\"}";

    这样的话,返回的结果是:

    JSONTestEntity [aa=null, bb=, cc=null, ss={:"'},]

    --------------------------------------------------------------------------------------分割线----------------------------------------------------------------------------------------


    好了,以上都是json-lib对java对象解析过程的分析。但是这只是知道一些现象的原因,并不是我想要的,我想要Date转成固定的日期格式,并且在为空的时候,反向序列化的时候不会有什么赋系统当前变量。怎么办!!!好办,我们只需要在java->json和json->java两个步骤上都插一脚即可。

    java->json:

    1,自定义JsonValueProcessor,实现其接口,具体实现网上有人写过了,我也是拷别人的,就不在此贴出来了,只贴个json-lib的接口定义。

    package net.sf.json.processors;
    
    import net.sf.json.JSONException;
    import net.sf.json.JsonConfig;
    
    /**
     * Base interface for custom serialization per property.
     *
     * @author Andres Almiray <aalmiray@users.sourceforge.net>
     */
    public interface JsonValueProcessor {
       /**
        * Processes the value an returns a suitable JSON value.
        *
        * @param value the input value
        * @return a valid JSON value that represents the input value
        * @throws JSONException if an error occurs during transformation
        */
       Object processArrayValue( Object value, JsonConfig jsonConfig );
    
       /**
        * Processes the value an returns a suitable JSON value.
        *
        * @param key the name of the property
        * @param value the value of the property
        * @return a valid JSON value that represents the input property
        * @throws JSONException if an error occurs during transformation
        */
       Object processObjectValue( String key, Object value, JsonConfig jsonConfig );
    }

    2,注册解析器

        public static String javaBeanToString(Object obj){
            JsonConfig cfg = new JsonConfig();
            cfg.registerJsonValueProcessor(java.sql.Timestamp.class, new DateJsonValueProcessor(null));
            cfg.registerJsonValueProcessor(java.util.Date.class, new DateJsonValueProcessor(null));
            cfg.registerJsonValueProcessor(java.sql.Date.class, new DateJsonValueProcessor(null));
            JSONObject jsonObject = JSONObject.fromObject(obj, cfg);
            return jsonObject.toString();
        }

    ok现在再执行java转json,会得到如下结果(PS:日期格式是我自己在DateJsonValueProcessor临时定义的):

    {"aa":"2017-11-09 15:38:02","bb":"2017-11-09 15:38:02","cc":"2017-11-09 15:38:02","ss":"{:\"'},"}

    json->java

    1,自定义ObjectMorpher

    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    import org.springframework.util.StringUtils;
    
    import net.sf.ezmorph.ObjectMorpher;
    
    public class UtilDateMorpher implements ObjectMorpher {
        private String format = "yyyy-MM-dd HH:mm:ss";
        
        /**
         * json转换成java object
         * @param value json字符串
         * 
         */
        @Override
        public Object morph(Object value) {
            SimpleDateFormat sf = new SimpleDateFormat(format);
            if(StringUtils.isEmpty(value)){
                return null;
            }
            try {
                return sf.parse((String)value);
            } catch (ParseException e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * 对哪种java对象进行解析
         */
        @Override
        public Class morphsTo() {
            return Date.class;
        }
    
        /**
         * 支持那种clazz类型的解析
         */
        @Override
        public boolean supports(Class clazz) {
            if(clazz == String.class){
                return true;
            }
            return false;
        }
    
    }

    这里我只是简单实现了一下,要注意supports方法,因为我们上一步已经把Date对象转换成字符串,所以这里传入的类型其实是string的

    2,将自定义的Morpher注册到json-lib里

        /**
         * 自定义增加如下三种日期型的解析
         */
        static{
            JSONUtils.getMorpherRegistry().registerMorpher(new UtilDateMorpher(), true);
            JSONUtils.getMorpherRegistry().registerMorpher(new SqlDateMorpher(), true);
            JSONUtils.getMorpherRegistry().registerMorpher(new TimeStampMorpher(), true);
        }

    由于json-lib的注册是写在静态代码块里的,所以这里我们只需要将这个写到我们调用json-lib工具类的静态代码块里即可。其他两种日期型的定义类似。

    好了,现在再执行结果,可以看到转化正常了。包括如果入参是“”的时候也给返回null

    JSONTestEntity [aa=Thu Nov 09 15:52:53 CST 2017, bb=2017-11-09 15:52:53.0, cc=2017-11-09, ss={:"'},]

    特别注意:要统一自定义日期格式,序列化和反序列化的日期格式要对应起来。

     

     

    以上就是两步转化中我们加入自定义实现的过程,至于详细的原理,包括json-lib转化的内部逻辑就不细说了,翻翻源代码就能看懂了

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-9-11 06:39 , Processed in 0.068063 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表