Browse Source

优化 JFinalJson 性能,并新增 Model、Record 字段名转驼峰格式的配置选项

James 5 years ago
parent
commit
4221fde77b

+ 96 - 258
src/main/java/com/jfinal/json/JFinalJson.java

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.com).
+ * Copyright (c) 2011-2020, James Zhan 詹波 (jfinal@126.com).
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,20 +16,10 @@
 
 package com.jfinal.json;
 
-import java.lang.reflect.Array;
-import java.lang.reflect.Method;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
+import java.util.function.Function;
+import com.jfinal.json.JFinalJsonKit.JsonResult;
+import com.jfinal.json.JFinalJsonKit.ToJson;
 import com.jfinal.kit.StrKit;
-import com.jfinal.plugin.activerecord.Model;
-import com.jfinal.plugin.activerecord.Record;
 
 /**
  * Json 转换 JFinal 实现.
@@ -42,14 +32,45 @@ import com.jfinal.plugin.activerecord.Record;
  * array			java.util.List
  * object			java.util.Map
  */
-@SuppressWarnings({"rawtypes", "unchecked"})
 public class JFinalJson extends Json {
 	
+	protected static final JFinalJsonKit kit = JFinalJsonKit.me;
+	
+	protected static final ThreadLocal<JsonResult> TL = ThreadLocal.withInitial(() -> new JsonResult());
+	
 	protected static int defaultConvertDepth = 16;
 	
 	protected int convertDepth = defaultConvertDepth;
 	protected String timestampPattern = "yyyy-MM-dd HH:mm:ss";
-	// protected String datePattern = "yyyy-MM-dd";
+	
+	public static JFinalJson getJson() {
+		return new JFinalJson();
+	}
+	
+	@Override
+	@SuppressWarnings({"rawtypes", "unchecked"})
+	public String toJson(Object object) {
+		if (useOldVersion) {
+			return toJsonUseOldVersion(object);
+		}
+		
+		if (object == null) {
+			return "null";
+		}
+		
+		JsonResult ret = TL.get();
+		try {
+			// 优先使用对象级的属性 datePattern, 然后才是全局性的 defaultDatePattern
+			String dp = datePattern != null ? datePattern : getDefaultDatePattern();
+			ret.init(dp, timestampPattern);
+			ToJson toJson = kit.getToJson(object);
+			toJson.toJson(object, convertDepth, ret);
+			return ret.toString();
+		}
+		finally {
+			ret.clear();
+		}
+	}
 	
 	/**
 	 * 设置全局性默认转换深度
@@ -77,266 +98,83 @@ public class JFinalJson extends Json {
 		return this;
 	}
 	
-	public static JFinalJson getJson() {
-		return new JFinalJson();
-	}
-	
-	protected String mapToJson(Map map, int depth) {
-		if(map == null) {
-			return "null";
-		}
-        StringBuilder sb = new StringBuilder();
-        boolean first = true;
-		Iterator iter = map.entrySet().iterator();
-		
-        sb.append('{');
-		while(iter.hasNext()){
-            if(first)
-                first = false;
-            else
-                sb.append(',');
-            
-			Map.Entry entry = (Map.Entry)iter.next();
-			toKeyValue(String.valueOf(entry.getKey()),entry.getValue(), sb, depth);
-		}
-        sb.append('}');
-		return sb.toString();
+	public static void setMaxBufferSize(int maxBufferSize) {
+		JFinalJsonKit.setMaxBufferSize(maxBufferSize);
 	}
 	
-	protected void toKeyValue(String key, Object value, StringBuilder sb, int depth){
-		sb.append('\"');
-        if(key == null)
-            sb.append("null");
-        else
-            escape(key, sb);
-		sb.append('\"').append(':');
-		
-		sb.append(toJson(value, depth));
+	/**
+	 * 将 Model 当成 Bean 只对 getter 方法进行转换
+	 * 
+	 * 默认值为 false,将使用 Model 内的 Map attrs 属性进行转换,不对 getter 方法进行转换
+	 * 优点是可以转换 sql 关联查询产生的动态字段,还可以转换 Model.put(...) 进来的数据
+	 * 
+	 * 配置为 true 时,将 Model 当成是传统的 java bean 对其 getter 方法进行转换,
+	 * 使用生成器生成过 base model 的情况下才可以使用此配置
+	 */
+	public static void setTreatModelAsBean(boolean treatModelAsBean) {
+		JFinalJsonKit.setTreatModelAsBean(treatModelAsBean);
 	}
 	
-	protected String iteratorToJson(Iterator iter, int depth) {
-        boolean first = true;
-        StringBuilder sb = new StringBuilder();
-        
-        sb.append('[');
-		while(iter.hasNext()){
-            if(first)
-                first = false;
-            else
-                sb.append(',');
-            
-			Object value = iter.next();
-			if(value == null){
-				sb.append("null");
-				continue;
-			}
-			sb.append(toJson(value, depth));
-		}
-        sb.append(']');
-		return sb.toString();
+	/**
+	 * 配置 Model、Record 字段名的转换函数
+	 * 
+	 * <pre>
+	 * 例子:
+	 *    JFinalJson.setModelAndRecordFieldNameConverter(fieldName -> {
+	 *		   return StrKit.toCamelCase(fieldName, true);
+	 *	  });
+	 *  
+	 *  以上例子中的方法 StrKit.toCamelCase(...) 的第二个参数可以控制大小写转化的细节
+	 *  可以查看其方法上方注释中的说明了解详情
+	 * </pre>
+	 */
+	public static void setModelAndRecordFieldNameConverter(Function<String, String>converter) {
+		JFinalJsonKit.setModelAndRecordFieldNameConverter(converter);
 	}
 	
 	/**
-	 * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters (U+0000 through U+001F).
+	 * 配置将 Model、Record 字段名转换为驼峰格式
+	 * 
+	 * 转换用到了 StrKit.toCamelCase(fieldName, true),具体转换规则参考
+	 * 该方法注释中的说明,注意字段名首先会被转换成小写字母,如果要改变转换规则
+	 * 可以使用 setModelAndRecordFieldNameConverter(Function func)
+	 * 定制自己的转换函数
 	 */
-	protected String escape(String s) {
-		if(s == null)
-			return null;
-        StringBuilder sb = new StringBuilder();
-        escape(s, sb);
-        return sb.toString();
-    }
-	
-	protected void escape(String s, StringBuilder sb) {
-		for(int i=0; i<s.length(); i++){
-			char ch = s.charAt(i);
-			switch(ch){
-			case '"':
-				sb.append("\\\"");
-				break;
-			case '\\':
-				sb.append("\\\\");
-				break;
-			case '\b':
-				sb.append("\\b");
-				break;
-			case '\f':
-				sb.append("\\f");
-				break;
-			case '\n':
-				sb.append("\\n");
-				break;
-			case '\r':
-				sb.append("\\r");
-				break;
-			case '\t':
-				sb.append("\\t");
-				break;
-			//case '/':
-			//	sb.append("\\/");
-			//	break;
-			default:
-				if((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF')) {
-					String str = Integer.toHexString(ch);
-					sb.append("\\u");
-					for(int k=0; k<4-str.length(); k++) {
-						sb.append('0');
-					}
-					sb.append(str.toUpperCase());
-				}
-				else{
-					sb.append(ch);
-				}
-			}
-		}
+	public static void setModelAndRecordFieldNameToCamelCase() {
+		JFinalJsonKit.setModelAndRecordFieldNameToCamelCase();
 	}
 	
-	public String toJson(Object object) {
-		return toJson(object, convertDepth);
+	public <T> T parse(String jsonString, Class<T> type) {
+		throw new RuntimeException("jfinal " + com.jfinal.core.Const.JFINAL_VERSION + 
+		"默认 json 实现暂不支持 json 到 object 的转换,建议使用 active recrord 的 Generator 生成 base model," +
+		"再通过 me.setJsonFactory(new MixedJsonFactory()) 来支持");
 	}
 	
-	protected String toJson(Object value, int depth) {
-		if(value == null || (depth--) < 0)
-			return "null";
-		
-		if(value instanceof String)
-			return "\"" + escape((String)value) + "\"";
-		
-		if(value instanceof Double){
-			if(((Double)value).isInfinite() || ((Double)value).isNaN())
-				return "null";
-			else
-				return value.toString();
-		}
-		
-		if(value instanceof Float){
-			if(((Float)value).isInfinite() || ((Float)value).isNaN())
-				return "null";
-			else
-				return value.toString();
-		}
-		
-		if(value instanceof Number)
-			return value.toString();
-		
-		if(value instanceof Boolean)
-			return value.toString();
-		
-		if (value instanceof java.util.Date) {
-			if (value instanceof java.sql.Timestamp) {
-				return "\"" + new SimpleDateFormat(timestampPattern).format(value) + "\"";
-			}
-			if (value instanceof java.sql.Time) {
-				return "\"" + value.toString() + "\"";
-			}
-			// 优先使用对象级的属性 datePattern, 然后才是全局性的 defaultDatePattern
-			String dp = datePattern != null ? datePattern : getDefaultDatePattern();
-			if (dp != null) {
-				return "\"" + new SimpleDateFormat(dp).format(value) + "\"";
-			} else {
-				return "" + ((java.util.Date)value).getTime();
-			}
-		}
-		
-		if(value instanceof Collection) {
-			return iteratorToJson(((Collection)value).iterator(), depth);
-		}
-		
-		if(value instanceof Map) {
-			return mapToJson((Map)value, depth);
-		}
-		
-		String result = otherToJson(value, depth);
-		if (result != null)
-			return result;
-		
-		// 类型无法处理时当作字符串处理,否则ajax调用返回时js无法解析
-		// return value.toString();
-		return "\"" + escape(value.toString()) + "\"";
-	}
+	// 以下代码用于切换回老版本实现 ----------------------------------------------------------------------------
 	
-	protected String otherToJson(Object value, int depth) {
-		if (value instanceof Character) {
-			return "\"" + escape(value.toString()) + "\"";
-		}
-		
-		if (value instanceof Model) {
-			Map map = com.jfinal.plugin.activerecord.CPI.getAttrs((Model)value);
-			return mapToJson(map, depth);
-		}
-		if (value instanceof Record) {
-			Map map = ((Record)value).getColumns();
-			return mapToJson(map, depth);
-		}
-		if (value.getClass().isArray()) {
-			int len = Array.getLength(value);
-			List<Object> list = new ArrayList<Object>(len);
-			for (int i=0; i<len; i++) {
-				list.add(Array.get(value, i));
-			}
-			return iteratorToJson(list.iterator(), depth);
-		}
-		if (value instanceof Iterator) {
-			return iteratorToJson((Iterator)value, depth);
-		}
-		if (value instanceof Enumeration) {
-			ArrayList<?> list = Collections.list((Enumeration<?>)value);
-			return iteratorToJson(list.iterator(), depth);
-		}
-		if (value instanceof Enum) {
-			return "\"" + ((Enum)value).toString() + "\"";
-		}
-		
-		return beanToJson(value, depth);
-	}
+	protected static boolean useOldVersion = false;
 	
-	protected String beanToJson(Object model, int depth) {
-		Map map = new HashMap();
-		Method[] methods = model.getClass().getMethods();
-		for (Method m : methods) {
-			if (m.getParameterCount() != 0) {
-				continue ;
-			}
-			
-			String methodName = m.getName();
-			int indexOfGet = methodName.indexOf("get");
-			if (indexOfGet == 0 && methodName.length() > 3) {	// Only getter
-				String attrName = methodName.substring(3);
-				if (!attrName.equals("Class")) {				// Ignore Object.getClass()
-					try {
-						Object value = m.invoke(model);
-						map.put(StrKit.firstCharToLowerCase(attrName), value);
-					} catch (Exception e) {
-						throw new RuntimeException(e.getMessage(), e);
-					}
-				}
-			}
-			else {
-				int indexOfIs = methodName.indexOf("is");
-				if (indexOfIs == 0 && methodName.length() > 2) {
-					String attrName = methodName.substring(2);
-					try {
-						Object value = m.invoke(model);
-						map.put(StrKit.firstCharToLowerCase(attrName), value);
-					} catch (Exception e) {
-						throw new RuntimeException(e.getMessage(), e);
-					}
-				}
-			}
-		}
-		return mapToJson(map, depth);
+	public static void setToUseOldVersion() {
+		useOldVersion = true;
 	}
 	
-	public <T> T parse(String jsonString, Class<T> type) {
-		throw new RuntimeException("jfinal " + com.jfinal.core.Const.JFINAL_VERSION + 
-		"默认 json 实现暂不支持 json 到 object 的转换,建议使用 active recrord 的 Generator 生成 base model," +
-		"再通过 me.setJsonFactory(new MixedJsonFactory()) 来支持");
+	/**
+	 * 由于 jfinal 4.9 版本对 JFinalJson 做了彻底的重构、优化,为确保兼容老版本,
+	 * 暂时保留老版本的 JFinalJson 实现,并提供该切回老版本的方法
+	 * 
+	 * 新版本实现在转换规则上与老版本完全一样,仅仅只做了重构与优化,所以大概率不用切换,
+	 * 经过几个迭代周期确认没有兼容性问题以后再删除,保障升级安全
+	 */
+	private String toJsonUseOldVersion(Object object) {
+		JFinalJsonOld json = JFinalJsonOld.getJson();
+		json.setConvertDepth(convertDepth);
+		json.setTimestampPattern(timestampPattern);
+		if (datePattern != null) {
+			json.setDatePattern(datePattern);
+		}
+		return json.toJson(object);
 	}
 }
 
 
 
-
-
-
-

+ 733 - 0
src/main/java/com/jfinal/json/JFinalJsonKit.java

@@ -0,0 +1,733 @@
+/**
+ * Copyright (c) 2011-2020, James Zhan 詹波 (jfinal@126.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jfinal.json;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import com.jfinal.kit.StrKit;
+import com.jfinal.kit.SyncWriteMap;
+import com.jfinal.plugin.activerecord.CPI;
+import com.jfinal.plugin.activerecord.Model;
+import com.jfinal.plugin.activerecord.Record;
+
+/**
+ * JFinalJsonKit
+ */
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class JFinalJsonKit {
+	
+	public static final JFinalJsonKit me = new JFinalJsonKit();
+	
+	// 缓存 ToJson 对象
+	protected static SyncWriteMap<Class<?>, ToJson<?>> map = new SyncWriteMap<>(512, 0.25F);
+	
+	// StringBuilder 最大缓冲区大小
+	protected static int maxBufferSize = 1024 * 512;
+	
+	// 将 Model 当成 Bean 只对 getter 方法进行转换
+	protected static boolean treatModelAsBean = false;
+	
+	// 对 Model 和 Record 的字段名进行转换的函数。例如转成驼峰形式对 oracle 支持更友好
+	protected static Function<String, String> modelAndRecordFieldNameConverter = null;
+	
+	public interface ToJson<T> {
+		void toJson(T value, int depth, JsonResult ret);
+	}
+	
+	public ToJson<?> getToJson(Object object) {
+		ToJson<?> ret = map.get(object.getClass());
+		if (ret == null) {
+			ret = createToJson(object);
+			map.putIfAbsent(object.getClass(), ret);
+		}
+		return ret;
+	}
+	
+	protected ToJson<?> createToJson(Object value) {
+		// 基础类型 -----------------------------------------
+		if (value instanceof String) {
+			return new StrToJson();
+		}
+		
+		if (value instanceof Number) {
+			if (value instanceof Integer) {
+				return new IntToJson();
+			}
+			if (value instanceof Long) {
+				return new LongToJson();
+			}
+			if (value instanceof Double) {
+				return new DoubleToJson();
+			}
+			if (value instanceof Float) {
+				return new FloatToJson();
+			}
+			return new NumberToJson();
+		}
+		
+		if (value instanceof Boolean) {
+			return new BooleanToJson();
+		}
+		
+		if (value instanceof Character) {
+			return new CharacterToJson();
+		}
+		
+		if (value instanceof Enum) {
+			return new EnumToJson();
+		}
+		
+		if (value instanceof java.util.Date) {
+			if (value instanceof Timestamp) {
+				return new TimestampToJson();
+			}
+			if (value instanceof Time) {
+				return new TimeToJson();
+			}
+			return new DateToJson();
+		}
+		
+		// 集合、Bean 类型,需要检测 depth ---------------------------------
+		if (! treatModelAsBean) {
+			if (value instanceof Model) {
+				return new ModelToJson();
+			}
+		}
+		
+		if (value instanceof Record) {
+			return new RecordToJson();
+		}
+		
+		if (value instanceof Map) {
+			return new MapToJson();
+		}
+		
+		if (value instanceof Collection) {
+			return new CollectionToJson();
+		}
+		
+		if (value.getClass().isArray()) {
+			return new ArrayToJson();
+		}
+		
+		if (value instanceof Enumeration) {
+			return new EnumerationToJson();
+		}
+		
+		if (value instanceof Iterator) {
+			return new IteratorToJson();
+		}
+		
+		if (value instanceof Iterable) {
+			return new IterableToJson();
+		}
+		
+		BeanToJson beanToJson = buildBeanToJson(value);
+		if (beanToJson != null) {
+			return beanToJson;
+		}
+		
+		return new UnknownToJson();
+	}
+	
+	static boolean checkDepth(int depth, JsonResult ret) {
+		if (depth < 0) {
+			ret.addNull();
+			return true;
+		} else {
+			return false;
+		}
+	}
+	
+	static class StrToJson implements ToJson<String> {
+		public void toJson(String str, int depth, JsonResult ret) {
+			escape(str, ret.sb);
+		}
+	}
+	
+	static class CharacterToJson implements ToJson<Character> {
+		public void toJson(Character ch, int depth, JsonResult ret) {
+			escape(ch.toString(), ret.sb);
+		}
+	}
+	
+	static class IntToJson implements ToJson<Integer> {
+		public void toJson(Integer value, int depth, JsonResult ret) {
+			ret.addInt(value);
+		}
+	}
+	
+	static class LongToJson implements ToJson<Long> {
+		public void toJson(Long value, int depth, JsonResult ret) {
+			ret.addLong(value);
+		}
+	}
+	
+	static class DoubleToJson implements ToJson<Double> {
+		public void toJson(Double value, int depth, JsonResult ret) {
+			if (value.isInfinite() || value.isNaN()) {
+				ret.addNull();
+			} else {
+				ret.addDouble(value);
+			}
+		}
+	}
+	
+	static class FloatToJson implements ToJson<Float> {
+		public void toJson(Float value, int depth, JsonResult ret) {
+			if (value.isInfinite() || value.isNaN()) {
+				ret.addNull();
+			} else {
+				ret.addFloat(value);
+			}
+		}
+	}
+	
+	// 接管 int、long、double、float 之外的 Number 类型
+	static class NumberToJson implements ToJson<Number> {
+		public void toJson(Number value, int depth, JsonResult ret) {
+			ret.addNumber(value);
+		}
+	}
+	
+	static class BooleanToJson implements ToJson<Boolean> {
+		public void toJson(Boolean value, int depth, JsonResult ret) {
+			ret.addBoolean(value);
+		}
+	}
+	
+	static class EnumToJson implements ToJson<Enum> {
+		public void toJson(Enum en, int depth, JsonResult ret) {
+			ret.addEnum(en);
+		}
+	}
+	
+	static class TimestampToJson implements ToJson<Timestamp> {
+		public void toJson(Timestamp ts, int depth, JsonResult ret) {
+			ret.addTimestamp(ts);
+		}
+	}
+	
+	static class TimeToJson implements ToJson<Time> {
+		public void toJson(Time t, int depth, JsonResult ret) {
+			ret.addTime(t);
+		}
+	}
+	
+	static class DateToJson implements ToJson<Date> {
+		public void toJson(Date value, int depth, JsonResult ret) {
+			ret.addDate(value);
+		}
+	}
+	
+	static class ModelToJson implements ToJson<Model> {
+		public void toJson(Model model, int depth, JsonResult ret) {
+			if (checkDepth(depth--, ret)) {
+				return ;
+			}
+			
+			Map<String, Object> attrs = CPI.getAttrs(model);
+			modelAndRecordToJson(attrs, depth, ret);
+		}
+	}
+	
+	static class RecordToJson implements ToJson<Record> {
+		public void toJson(Record record, int depth, JsonResult ret) {
+			if (checkDepth(depth--, ret)) {
+				return ;
+			}
+			
+			Map<String, Object> columns = record.getColumns();
+			modelAndRecordToJson(columns, depth, ret);
+		}
+	}
+	
+	static void modelAndRecordToJson(Map<String, Object> map, int depth, JsonResult ret) {
+		Iterator iter = map.entrySet().iterator();
+		boolean first = true;
+		ret.addChar('{');
+		while (iter.hasNext()) {
+			if (first) {
+				first = false;
+			} else {
+				ret.addChar(',');
+			}
+			
+			Map.Entry<String, Object> entry = (Map.Entry)iter.next();
+			
+			String fieldName = entry.getKey();
+			if (modelAndRecordFieldNameConverter != null) {
+				fieldName = modelAndRecordFieldNameConverter.apply(fieldName);
+			}
+			ret.addStrNoEsc(fieldName);
+			
+			ret.addChar(':');
+			
+			Object value = entry.getValue();
+			if (value != null) {
+				ToJson tj = me.getToJson(value);
+				tj.toJson(value, depth, ret);
+			} else {
+				ret.addNull();
+			}
+		}
+		ret.addChar('}');
+	}
+	
+	static class MapToJson implements ToJson<Map<?, ?>> {
+		public void toJson(Map<?, ?> map, int depth, JsonResult ret) {
+			if (checkDepth(depth--, ret)) {
+				return ;
+			}
+			
+			mapToJson(map, depth, ret);
+		}
+	}
+	
+	static void mapToJson(Map<?, ?> map, int depth, JsonResult ret) {
+		Iterator iter = map.entrySet().iterator();
+		boolean first = true;
+		ret.addChar('{');
+		while (iter.hasNext()) {
+			if (first) {
+				first = false;
+			} else {
+				ret.addChar(',');
+			}
+			
+			Map.Entry entry = (Map.Entry)iter.next();
+			ret.addMapKey(entry.getKey());
+			ret.addChar(':');
+			
+			Object value = entry.getValue();
+			if (value != null) {
+				ToJson tj = me.getToJson(value);
+				tj.toJson(value, depth, ret);
+			} else {
+				ret.addNull();
+			}
+		}
+		ret.addChar('}');
+	}
+	
+	static class CollectionToJson implements ToJson<Collection> {
+		public void toJson(Collection c, int depth, JsonResult ret) {
+			if (checkDepth(depth--, ret)) {
+				return ;
+			}
+			
+			iteratorToJson(c.iterator(), depth, ret);
+		}
+	}
+	
+	static class ArrayToJson implements ToJson<Object> {
+		public void toJson(Object object, int depth, JsonResult ret) {
+			if (checkDepth(depth--, ret)) {
+				return ;
+			}
+			
+			iteratorToJson(new ArrayIterator(object), depth, ret);
+		}
+	}
+	
+	static class ArrayIterator implements Iterator<Object> {
+		private Object array;
+		private int size;
+		private int index;
+		public ArrayIterator(Object array) {
+			this.array = array;
+			this.size = Array.getLength(array);
+			this.index = 0;
+		}
+		public boolean hasNext() {
+			return index < size;
+		}
+		public Object next() {
+			return Array.get(array, index++);
+		}
+	}
+	
+	static class EnumerationToJson implements ToJson<Enumeration> {
+		public void toJson(Enumeration en, int depth, JsonResult ret) {
+			if (checkDepth(depth--, ret)) {
+				return ;
+			}
+			
+			ArrayList list = Collections.list(en);
+			iteratorToJson(list.iterator(), depth, ret);
+		}
+	}
+	
+	static class IteratorToJson implements ToJson<Iterator> {
+		public void toJson(Iterator it, int depth, JsonResult ret) {
+			if (checkDepth(depth--, ret)) {
+				return ;
+			}
+			
+			iteratorToJson(it, depth, ret);
+		}
+	}
+	
+	static void iteratorToJson(Iterator it, int depth, JsonResult ret) {
+		boolean first = true;
+		ret.addChar('[');
+		while (it.hasNext()) {
+			if (first) {
+				first = false;
+			} else {
+				ret.addChar(',');
+			}
+			
+			Object value = it.next();
+			if (value != null) {
+				ToJson tj = me.getToJson(value);
+				tj.toJson(value, depth, ret);
+			} else {
+				ret.addNull();
+			}
+		}
+		ret.addChar(']');
+	}
+	
+	static class IterableToJson implements ToJson<Iterable> {
+		public void toJson(Iterable iterable, int depth, JsonResult ret) {
+			if (checkDepth(depth--, ret)) {
+				return ;
+			}
+			
+			iteratorToJson(iterable.iterator(), depth, ret);
+		}
+	}
+	
+	static class BeanToJson implements ToJson<Object> {
+		private static final Object[] NULL_ARGS = new Object[0];
+		private String[] fields;
+		private Method[] methods;
+		
+		public BeanToJson(String[] fields, Method[] methods) {
+			if (fields.length != methods.length) {
+				throw new IllegalArgumentException("fields 与 methods 长度必须相同");
+			}
+			
+			this.fields = fields;
+			this.methods = methods;
+		}
+		
+		public void toJson(Object bean, int depth, JsonResult ret) {
+			if (checkDepth(depth--, ret)) {
+				return ;
+			}
+			
+			try {
+				ret.addChar('{');
+				for (int i = 0; i < fields.length; i++) {
+					if (i > 0) {
+						ret.addChar(',');
+					}
+					
+					ret.addStrNoEsc(fields[i]);
+					ret.addChar(':');
+					
+					Object value = methods[i].invoke(bean, NULL_ARGS);
+					if (value != null) {
+						ToJson tj = me.getToJson(value);
+						tj.toJson(value, depth, ret);
+					} else {
+						ret.addNull();
+					}
+				}
+				ret.addChar('}');
+			} catch (ReflectiveOperationException e) {
+				throw new RuntimeException(e);
+			}
+		}
+	}
+	
+	/**
+	 * 存在 getter/is 方法返回 BeanToJson,否则返回 null
+	 */
+	public static BeanToJson buildBeanToJson(Object bean) {
+		List<String> fields = new ArrayList<>();
+		List<Method> methods = new ArrayList<>();
+		
+		Method[] methodArray = bean.getClass().getMethods();
+		for (Method m : methodArray) {
+			if (m.getParameterCount() != 0 || m.getReturnType() == void.class) {
+				continue ;
+			}
+			
+			String methodName = m.getName();
+			int indexOfGet = methodName.indexOf("get");
+			if (indexOfGet == 0 && methodName.length() > 3) {	// Only getter
+				String attrName = methodName.substring(3);
+				if (!attrName.equals("Class")) {				// Ignore Object.getClass()
+					fields.add(StrKit.firstCharToLowerCase(attrName));
+					methods.add(m);
+				}
+			}
+			else {
+				int indexOfIs = methodName.indexOf("is");
+				if (indexOfIs == 0 && methodName.length() > 2) {
+					String attrName = methodName.substring(2);
+					fields.add(StrKit.firstCharToLowerCase(attrName));
+					methods.add(m);
+				}
+			}
+		}
+		
+		int size = fields.size();
+		if (size > 0) {
+			return new BeanToJson(fields.toArray(new String[size]), methods.toArray(new Method[size]));
+		} else {
+			return null;
+		}
+	}
+	
+	static class UnknownToJson implements ToJson<Object> {
+		public void toJson(Object object, int depth, JsonResult ret) {
+			// 未知类型无法处理时当作字符串处理,否则 ajax 调用返回时 js 无法解析
+			ret.addUnknown(object);
+		}
+	}
+	
+	/**
+	 * JsonResult 用于存放 json 生成结果,结合 ThreadLocal 进行资源重用
+	 */
+	static class JsonResult {
+		
+		// 缓存 SimpleDateFormat
+		Map<String, SimpleDateFormat> formats = new HashMap<>();
+		
+		// StringBuilder 内部对 int、long、double、float 数据写入有优化
+		StringBuilder sb = new StringBuilder();
+		
+		String datePattern;
+		String timestampPattern;
+		
+		public void init(String datePattern, String timestampPattern) {
+			this.datePattern = datePattern;
+			this.timestampPattern = timestampPattern;
+		}
+		
+		public String toString() {
+			return sb.toString();
+		}
+		
+		public void clear() {
+			// 释放空间占用过大的缓存
+			if (sb.length() > maxBufferSize) {
+				sb = new StringBuilder(Math.max(1024, maxBufferSize / 2));
+			} else {
+				sb.setLength(0);
+			}
+		}
+		
+		public void addChar(char ch) {
+			sb.append(ch);
+		}
+		
+		public void addNull() {
+			// sb.append((String)null);
+			sb.append("null");
+		}
+		
+		public void addStrNoEsc(String str) {
+			sb.append('\"').append(str).append('\"');
+		}
+		
+		public void addInt(int i) {
+			sb.append(i);
+		}
+		
+		public void addLong(long l) {
+			sb.append(l);
+		}
+		
+		public void addDouble(double d) {
+			sb.append(d);
+		}
+		
+		public void addFloat(float f) {
+			sb.append(f);
+		}
+		
+		public void addNumber(Number n) {
+			sb.append(n.toString());
+		}
+		
+		public void addBoolean(boolean b) {
+			sb.append(b);
+		}
+		
+		public void addEnum(Enum en) {
+			sb.append('\"').append(en.toString()).append('\"');
+		}
+		
+		SimpleDateFormat getFormat(String pattern) {
+			SimpleDateFormat ret = formats.get(pattern);
+			if (ret == null) {
+				ret = new SimpleDateFormat(pattern);
+				formats.put(pattern, ret);
+			}
+			return ret;
+		}
+		
+		public void addTimestamp(Timestamp ts) {
+			sb.append('\"').append(getFormat(timestampPattern).format(ts)).append('\"');
+		}
+		
+		public void addTime(Time t) {
+			sb.append('\"').append(t.toString()).append('\"');
+		}
+		
+		public void addDate(Date d) {
+			if (datePattern != null) {
+				sb.append('\"').append(getFormat(datePattern).format(d)).append('\"');
+			} else {
+				sb.append(d.getTime());
+			}
+		}
+		
+		public void addMapKey(Object value) {
+			escape(String.valueOf(value), sb);
+		}
+		
+		public void addUnknown(Object obj) {
+			escape(obj.toString(), sb);
+		}
+	}
+	
+	/**
+	 * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters (U+0000 through U+001F).
+	 */
+	public static void escape(String s, StringBuilder sb) {
+		sb.append('\"');
+		
+		for (int i = 0, len = s.length(); i < len; i++) {
+			char ch = s.charAt(i);
+			switch (ch) {
+			case '"':
+				sb.append("\\\"");
+				break;
+			case '\\':
+				sb.append("\\\\");
+				break;
+			case '\b':
+				sb.append("\\b");
+				break;
+			case '\f':
+				sb.append("\\f");
+				break;
+			case '\n':
+				sb.append("\\n");
+				break;
+			case '\r':
+				sb.append("\\r");
+				break;
+			case '\t':
+				sb.append("\\t");
+				break;
+			//case '/':
+			//	sb.append("\\/");
+			//	break;
+			default:
+				if ((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF')) {
+					String str = Integer.toHexString(ch);
+					sb.append("\\u");
+					for (int k = 0; k < 4 - str.length(); k++) {
+						sb.append('0');
+					}
+					sb.append(str.toUpperCase());
+				}
+				else {
+					sb.append(ch);
+				}
+			}
+		}
+		
+		sb.append('\"');
+	}
+	
+	public static void setMaxBufferSize(int maxBufferSize) {
+		int size = 1024 * 1;
+		if (maxBufferSize < size) {
+			throw new IllegalArgumentException("maxBufferSize can not less than " + size);
+		}
+		JFinalJsonKit.maxBufferSize = maxBufferSize;
+	}
+	
+	/**
+	 * 将 Model 当成 Bean 只对 getter 方法进行转换
+	 * 
+	 * 默认值为 false,将使用 Model 内的 Map attrs 属性进行转换,不对 getter 方法进行转换
+	 * 优点是可以转换 sql 关联查询产生的动态字段,还可以转换 Model.put(...) 进来的数据
+	 * 
+	 * 配置为 true 时,将 Model 当成是传统的 java bean 对其 getter 方法进行转换,
+	 * 使用生成器生成过 base model 的情况下才可以使用此配置
+	 */
+	public static void setTreatModelAsBean(boolean treatModelAsBean) {
+		JFinalJsonKit.treatModelAsBean = treatModelAsBean;
+	}
+	
+	/**
+	 * 配置 Model、Record 字段名的转换函数
+	 * 
+	 * <pre>
+	 * 例子:
+	 *    JFinalJson.setModelAndRecordFieldNameConverter(fieldName -> {
+	 *		   return StrKit.toCamelCase(fieldName, true);
+	 *	  });
+	 *  
+	 *  以上例子中的方法 StrKit.toCamelCase(...) 的第二个参数可以控制大小写转化的细节
+	 *  可以查看其方法上方注释中的说明了解详情
+	 * </pre>
+	 */
+	public static void setModelAndRecordFieldNameConverter(Function<String, String>converter) {
+		JFinalJsonKit.modelAndRecordFieldNameConverter = converter;
+	}
+	
+	/**
+	 * 配置将 Model、Record 字段名转换为驼峰格式
+	 * 
+	 * 转换用到了 StrKit.toCamelCase(fieldName, true),具体转换规则参考
+	 * 该方法注释中的说明,注意字段名首先会被转换成小写字母,如果要改变转换规则
+	 * 可以使用 setModelAndRecordFieldNameConverter(Function func)
+	 * 定制自己的转换函数
+	 */
+	public static void setModelAndRecordFieldNameToCamelCase() {
+		modelAndRecordFieldNameConverter = (fieldName) -> {
+			return StrKit.toCamelCase(fieldName, true);
+		};
+	}
+}
+
+
+
+

+ 350 - 0
src/main/java/com/jfinal/json/JFinalJsonOld.java

@@ -0,0 +1,350 @@
+/**
+ * Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jfinal.json;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import com.jfinal.kit.StrKit;
+import com.jfinal.plugin.activerecord.Model;
+import com.jfinal.plugin.activerecord.Record;
+
+/**
+ * Json 转换 JFinal 实现.
+ * 
+ * json 到 java 类型转换规则:
+ * string			java.lang.String
+ * number			java.lang.Number
+ * true|false		java.lang.Boolean
+ * null				null
+ * array			java.util.List
+ * object			java.util.Map
+ */
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class JFinalJsonOld extends Json {
+	
+	protected static int defaultConvertDepth = 16;
+	
+	protected int convertDepth = defaultConvertDepth;
+	protected String timestampPattern = "yyyy-MM-dd HH:mm:ss";
+	// protected String datePattern = "yyyy-MM-dd";
+	
+	/**
+	 * 设置全局性默认转换深度
+	 */
+	public static void setDefaultConvertDepth(int defaultConvertDepth) {
+		if (defaultConvertDepth < 2) {
+			throw new IllegalArgumentException("defaultConvertDepth depth can not less than 2.");
+		}
+		JFinalJsonOld.defaultConvertDepth = defaultConvertDepth;
+	}
+	
+	public JFinalJsonOld setConvertDepth(int convertDepth) {
+		if (convertDepth < 2) {
+			throw new IllegalArgumentException("convert depth can not less than 2.");
+		}
+		this.convertDepth = convertDepth;
+		return this;
+	}
+	
+	public JFinalJsonOld setTimestampPattern(String timestampPattern) {
+		if (StrKit.isBlank(timestampPattern)) {
+			throw new IllegalArgumentException("timestampPattern can not be blank.");
+		}
+		this.timestampPattern = timestampPattern;
+		return this;
+	}
+	
+	public static JFinalJsonOld getJson() {
+		return new JFinalJsonOld();
+	}
+	
+	protected String mapToJson(Map map, int depth) {
+		if(map == null) {
+			return "null";
+		}
+        StringBuilder sb = new StringBuilder();
+        boolean first = true;
+		Iterator iter = map.entrySet().iterator();
+		
+        sb.append('{');
+		while(iter.hasNext()){
+            if(first)
+                first = false;
+            else
+                sb.append(',');
+            
+			Map.Entry entry = (Map.Entry)iter.next();
+			toKeyValue(String.valueOf(entry.getKey()),entry.getValue(), sb, depth);
+		}
+        sb.append('}');
+		return sb.toString();
+	}
+	
+	protected void toKeyValue(String key, Object value, StringBuilder sb, int depth){
+		sb.append('\"');
+        if(key == null)
+            sb.append("null");
+        else
+            escape(key, sb);
+		sb.append('\"').append(':');
+		
+		sb.append(toJson(value, depth));
+	}
+	
+	protected String iteratorToJson(Iterator iter, int depth) {
+        boolean first = true;
+        StringBuilder sb = new StringBuilder();
+        
+        sb.append('[');
+		while(iter.hasNext()){
+            if(first)
+                first = false;
+            else
+                sb.append(',');
+            
+			Object value = iter.next();
+			if(value == null){
+				sb.append("null");
+				continue;
+			}
+			sb.append(toJson(value, depth));
+		}
+        sb.append(']');
+		return sb.toString();
+	}
+	
+	/**
+	 * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters (U+0000 through U+001F).
+	 */
+	protected String escape(String s) {
+		if(s == null)
+			return null;
+        StringBuilder sb = new StringBuilder();
+        escape(s, sb);
+        return sb.toString();
+    }
+	
+	protected void escape(String s, StringBuilder sb) {
+		for(int i=0; i<s.length(); i++){
+			char ch = s.charAt(i);
+			switch(ch){
+			case '"':
+				sb.append("\\\"");
+				break;
+			case '\\':
+				sb.append("\\\\");
+				break;
+			case '\b':
+				sb.append("\\b");
+				break;
+			case '\f':
+				sb.append("\\f");
+				break;
+			case '\n':
+				sb.append("\\n");
+				break;
+			case '\r':
+				sb.append("\\r");
+				break;
+			case '\t':
+				sb.append("\\t");
+				break;
+			//case '/':
+			//	sb.append("\\/");
+			//	break;
+			default:
+				if((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF')) {
+					String str = Integer.toHexString(ch);
+					sb.append("\\u");
+					for(int k=0; k<4-str.length(); k++) {
+						sb.append('0');
+					}
+					sb.append(str.toUpperCase());
+				}
+				else{
+					sb.append(ch);
+				}
+			}
+		}
+	}
+	
+	public String toJson(Object object) {
+		return toJson(object, convertDepth);
+	}
+	
+	protected String toJson(Object value, int depth) {
+		if(value == null || (depth--) < 0)
+			return "null";
+		
+		if(value instanceof String)
+			return "\"" + escape((String)value) + "\"";
+		
+		if(value instanceof Double){
+			if(((Double)value).isInfinite() || ((Double)value).isNaN())
+				return "null";
+			else
+				return value.toString();
+		}
+		
+		if(value instanceof Float){
+			if(((Float)value).isInfinite() || ((Float)value).isNaN())
+				return "null";
+			else
+				return value.toString();
+		}
+		
+		if(value instanceof Number)
+			return value.toString();
+		
+		if(value instanceof Boolean)
+			return value.toString();
+		
+		if (value instanceof java.util.Date) {
+			if (value instanceof java.sql.Timestamp) {
+				return "\"" + new SimpleDateFormat(timestampPattern).format(value) + "\"";
+			}
+			if (value instanceof java.sql.Time) {
+				return "\"" + value.toString() + "\"";
+			}
+			// 优先使用对象级的属性 datePattern, 然后才是全局性的 defaultDatePattern
+			String dp = datePattern != null ? datePattern : getDefaultDatePattern();
+			if (dp != null) {
+				return "\"" + new SimpleDateFormat(dp).format(value) + "\"";
+			} else {
+				return "" + ((java.util.Date)value).getTime();
+			}
+		}
+		
+		if(value instanceof Collection) {
+			return iteratorToJson(((Collection)value).iterator(), depth);
+		}
+		
+		if(value instanceof Map) {
+			return mapToJson((Map)value, depth);
+		}
+		
+		String result = otherToJson(value, depth);
+		if (result != null)
+			return result;
+		
+		// 类型无法处理时当作字符串处理,否则ajax调用返回时js无法解析
+		// return value.toString();
+		return "\"" + escape(value.toString()) + "\"";
+	}
+	
+	protected String otherToJson(Object value, int depth) {
+		if (value instanceof Character) {
+			return "\"" + escape(value.toString()) + "\"";
+		}
+		
+		if (value instanceof Model) {
+			Map map = com.jfinal.plugin.activerecord.CPI.getAttrs((Model)value);
+			return mapToJson(map, depth);
+		}
+		if (value instanceof Record) {
+			Map map = ((Record)value).getColumns();
+			return mapToJson(map, depth);
+		}
+		if (value.getClass().isArray()) {
+			int len = Array.getLength(value);
+			List<Object> list = new ArrayList<Object>(len);
+			for (int i=0; i<len; i++) {
+				list.add(Array.get(value, i));
+			}
+			return iteratorToJson(list.iterator(), depth);
+		}
+		if (value instanceof Iterator) {
+			return iteratorToJson((Iterator)value, depth);
+		}
+		if (value instanceof Enumeration) {
+			ArrayList<?> list = Collections.list((Enumeration<?>)value);
+			return iteratorToJson(list.iterator(), depth);
+		}
+		if (value instanceof Enum) {
+			return "\"" + ((Enum)value).toString() + "\"";
+		}
+		
+		return beanToJson(value, depth);
+	}
+	
+	protected String beanToJson(Object model, int depth) {
+		Map map = new HashMap();
+		Method[] methods = model.getClass().getMethods();
+		for (Method m : methods) {
+			if (m.getParameterCount() != 0) {
+				continue ;
+			}
+			
+			String methodName = m.getName();
+			int indexOfGet = methodName.indexOf("get");
+			if (indexOfGet == 0 && methodName.length() > 3) {	// Only getter
+				String attrName = methodName.substring(3);
+				if (!attrName.equals("Class")) {				// Ignore Object.getClass()
+					try {
+						Object value = m.invoke(model);
+						map.put(StrKit.firstCharToLowerCase(attrName), value);
+					} catch (Exception e) {
+						throw new RuntimeException(e.getMessage(), e);
+					}
+				}
+			}
+			else {
+				int indexOfIs = methodName.indexOf("is");
+				if (indexOfIs == 0 && methodName.length() > 2) {
+					String attrName = methodName.substring(2);
+					try {
+						Object value = m.invoke(model);
+						map.put(StrKit.firstCharToLowerCase(attrName), value);
+					} catch (Exception e) {
+						throw new RuntimeException(e.getMessage(), e);
+					}
+				}
+			}
+		}
+		return mapToJson(map, depth);
+	}
+	
+	// ----------------------------------------------------------------------
+	
+	protected FastJson fastJson;
+	
+	public <T> T parse(String jsonString, Class<T> type) {
+		if (fastJson == null) {
+			fastJson = FastJson.getJson();
+		}
+		if (datePattern != null) {
+			fastJson.setDatePattern(datePattern);
+		}
+		return fastJson.parse(jsonString, type);
+	}
+}
+
+
+
+
+
+
+