關於json-lib中日期類型轉換的分析與問題解決


說明:本文中的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轉化的內部邏輯就不細說了,翻翻源代碼就能看懂了

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM