Java 原生 JSON 序列化

论坛 期权论坛 脚本     
匿名技术用户   2020-12-22 13:16   11   0

首先要说的是,笔者之前着实足够奇葩,净干别人未干过的事情,居然会想到用 JS 引擎来转换 JSON(《用 Rhino/Nashorn 代替第三方 JSON 转换库》《使用 Rhino 作为 Java 的 JSON 解析/转换包》),几经思考后,还是决然毅然放弃这个不切实际的想法,老老实实去写转换函数,几经打磨,有了下面“序列化” JSON 的 toJSON() 函数。

原理分析

请先过目源码。
完整源码在:
https://gitee.com/sp42_admin/ajaxjs/blob/master/ajaxjs-base/src/main/java/com/ajaxjs/js/JsonHelper.java

 /**
  * 对 Object 尝试类型检测,将其合适地转换为 JSON 字符串返回。
  * 
  * @param obj 任意对象
  * @return JSON 字符串
  */
 public static String toJson(Object obj) {
  if (obj == null) {
   return null;
  } else if (obj instanceof String) {
   return '\"' + obj.toString().replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r") + '\"';
  } else if (obj instanceof Double) {
   return obj + "";
  } else if (obj instanceof Boolean || obj instanceof Number) {
   return obj.toString();
  } else if (obj instanceof Date) {
   return '\"' + CommonUtil.SimpleDateFormatFactory(CommonUtil.commonDateFormat).format((Date) obj) + '\"';
  } else if (obj.getClass() == Integer[].class) {
   return jsonArr((Integer[]) obj, v -> v + "");
  } else if (obj.getClass() == int[].class) {
   Integer[] arr = Arrays.stream((int[]) obj).boxed().toArray(Integer[]::new);
   return jsonArr(arr, v -> v + "");
  } else if (obj instanceof Long[]) {
   return jsonArr((Long[]) obj, v -> v.toString());
  } else if (obj instanceof long[]) {
   Long[] arr = Arrays.stream((long[]) obj).boxed().toArray(Long[]::new);
   return jsonArr(arr, v -> v.toString());
  } else if (obj instanceof String[]) {
   return jsonArr((String[]) obj, v -> "\"" + v + "\"");
  } else if (obj instanceof Map) {
   return stringifyMap((Map<?, ?>) obj);
  } else if (obj instanceof Map[]) {
   return jsonArr((Map<?, ?>[]) obj, JsonHelper::stringifyMap);
  } else if (obj instanceof BaseModel) {
   return beanToJson((BaseModel) obj);
  } else if (obj instanceof BaseModel[]) {
   return jsonArr((BaseModel[]) obj, JsonHelper::beanToJson);
  } else if (obj instanceof List) {
   List<?> list = (List<?>) obj;

   if (list.size() > 0) {
    if (list.get(0) instanceof Integer) {
     return toJson(list.toArray(new Integer[list.size()]));
    } else if (list.get(0) instanceof String) {
     return toJson(list.toArray(new String[list.size()]));
    } else if (list.get(0) instanceof Map) { // Map 类型的输出
     return toJson(list.toArray(new Map[list.size()]));
    } else if (list.get(0) instanceof BaseModel) { // Bean
     return toJson(list.toArray(new BaseModel[list.size()]));
    }
   } else {
    return "[]";
   }
  } else if (obj instanceof Object[]) {
   return jsonArr((Object[]) obj, JsonHelper::toJson);
  } else if (obj instanceof Object) { // 普通 Java Object
   List<String> arr = new ArrayList<>();
   for (Field field : obj.getClass().getDeclaredFields()) {
    field.setAccessible(true);

    String key = field.getName();
    if (key.indexOf("this$") != -1)
     continue;

    Object _obj = null;
    try {
     _obj = field.get(obj);
    } catch (IllegalAccessException e) {
     e.printStackTrace();
    }

    arr.add('\"' + key + "\":" + toJson(_obj));
   }

   return '{' + String.join(",", arr) + '}';
  } else {
   throw new RuntimeException("不支持数据类型");
  }

  return null;
 }

这函数比较长,很多的 if else 判断,目的是一个方法包办所有常见类型到 JSON 的转换。他可以涵盖下面类型,作为输入的参数:

  • null
  • boolean/Boolean
  • String, String[], List<String>
  • int/Integer, int[]/Integer[], List<Integer>
  • long/Long, long[]/Long[], List<Long>
  • Date 日期
  • Map/Map[]/List<Map>
  • BaseModel/BaseModel[]/List<BaseModel> BaseModel 是我框架所有 Bean 的基类
  • Object, 普通 Java Object
    转换逻辑简单说明如下:首先 null 值自然返回 null;boolean 和 number 数字类型(包括 int/long 等等)转换字符串即可;字符串的话作适当的转义然后两旁套上双引号即可——这些都是比较简单的转换。

数组判断的话,首先有两种写法,是等价的,一种是 obj instanceof String[],另外一种是 obj.getClass() == int[].class,而且注意 Integer[] 和 int[] 类型的判断是不同的,要两种情况都要考虑。

JSON 中没有区分 Array 和 List,它只有 List。即使在 Java, Array 和 List 都离不开遍历的操作,我们把弄一块遍历好了。

/**
 * 输入任意类型数组,在 fn 作适当的转换,返回 JSON 字符串
 * 
 * @param o 数组
 * @param fn 元素处理器,返回元素 JSON 字符串
 * @return 数组的 JSON 字符串
 */
public static <T> String jsonArr(T[] o, Function<T, String> fn) {
 if (o.length == 0)
  return "[]";

 StringBuilder sb = new StringBuilder();

 for (int i = 0; i < o.length; i++) {
  sb.append(fn.apply((T) o[i]));
  if (i != (o.length - 1))
   sb.append(", ");
 }

 return '[' + sb.toString() + ']';
}

int[]/long[] 这些类型不能直接放在函数接口里面使用,因为 Java 的泛型就是要装箱类型的,故先转换一下,例如 Integer[] arr = Arrays.stream((int[]) obj).boxed().toArray(Integer[]::new);。值得一提的是,如果集合元素个数为零,那么返回空数组 [],而不是 null 或者空字符串。

Map 和 Bean 都有直接的转换函数,分别是 JsonHelper::stringifyMap 和 JsonHelper::beanToJson,都是些常规性的操作,比较多的是递归操作,其它亮点不多,——可自行看源码了解。若是数组的话则递归一下即可。

至于 List<?> 的判断,则是取出其第一个元素作为类型的判断,然后归纳到数组的处理方法中(你不能依然还是拿 List 去递归,那样会死循环)。

用法

下面是用法简介:

assertEquals(null, JsonHelper.toJson(null));
assertEquals("1.0", JsonHelper.toJson(1D));
assertEquals("true", JsonHelper.toJson(true));
assertEquals("1", JsonHelper.toJson(1));
assertEquals("1", JsonHelper.toJson(1L));
assertEquals("\"2018-02-20 00:00:00\"", JsonHelper.toJson((Date) MappingValue.objectCast("2018-2-20", Date.class)));

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
assertEquals("[1, 2, 3]", JsonHelper.toJson(list));
assertEquals("[1, 2, 3]", JsonHelper.toJson(new Integer[] { 1, 2, 3 }));
assertEquals("[1, 2, 3]", JsonHelper.toJson(new int[] { 1, 2, 3 }));

List<String> list2 = new ArrayList<>();
list2.add("1");
list2.add("2");
list2.add("3");
assertEquals("[\"1\", \"2\", \"3\"]", JsonHelper.toJson(list2));
assertEquals("[\"1\", \"2\", \"3\"]", JsonHelper.toJson(new String[] { "1", "2", "3" }));

Map<String, Object> map = new HashMap<>();
assertEquals("{}", JsonHelper.toJson(map));
map.put("foo", "bar");
assertEquals("{\"foo\":\"bar\"}", JsonHelper.toJson(map));
map.put("bar", 1);
assertEquals("{\"bar\":1,\"foo\":\"bar\"}", JsonHelper.toJson(map));

List<Map<String, Object>> list3 = new ArrayList<>();
assertEquals("[]", JsonHelper.toJson(list3));
list3.add(map);
list3.add(map);
assertEquals("[{\"bar\":1,\"foo\":\"bar\"}, {\"bar\":1,\"foo\":\"bar\"}]", JsonHelper.toJson(list3));

手工 JSON 输出:

/**
 * 检查是否重复的手机号码
 * 
 * @param phone 手机号码
 * @return true=已存在
 */
public String checkIfUserPhoneRepeat(String phone) {
 LOGGER.info("检查是否重复的手机号码:" + phone);
 return "{\"isRepeat\":" + getService().checkIfUserPhoneRepeat(phone) + "}";
}

转换为 toJson() 写法:

/**
 * 检查是否重复的手机号码
 * 
 * @param phone 手机号码
 * @return true=已存在
 */
public String checkIfUserPhoneRepeat(String phone) {
 LOGGER.info("检查是否重复的手机号码:" + phone);

 return toJson(new HashMap<String, Boolean>() {
  private static final long serialVersionUID = -5033049204280154615L;
  {
   put("isRepeat", getService().checkIfUserPhoneRepeat(phone));
  }
 });
}

可见通过一个 HashMap 来作为健对值的容器了。

普通 Java Object 转换 JSON

普通 Java Object 也可以表达 Key、Value 的结构,如下列,

@Test
public void testStringifySimpleObject() {
 Object obj = new Object() {
  @SuppressWarnings("unused")
  public Object NULL = null;
  @SuppressWarnings("unused")
  public String str = null;
  @SuppressWarnings("unused")
  public Boolean isOk = false;
  @SuppressWarnings("unused")
  public Integer n0 = 0;
  @SuppressWarnings("unused")
  public Number n1 = 111;
  @SuppressWarnings("unused")
  public int n2 = 222;
  // @SuppressWarnings("unused")
  // public Date date = new Date();
  @SuppressWarnings("unused")
  public String msg = "Hello world";
  @SuppressWarnings("unused")
  public Object[] arr = new Object[] { 1, "2", null };
 };

 String jsonStr = JsonHelper.toJson(obj);
 // 输出 {"foo":"11","bar":"2222"}
 assertNotNull(jsonStr);
 assertEquals("{\"NULL\":null,\"str\":null,\"isOk\":false,\"n0\":0,\"n1\":111,\"n2\":222,\"msg\":\"Hello world\",\"arr\":[1, \"2\", null]}", jsonStr);
}

toJSON() 也是支持这种结构的。

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP