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