JSONX-在Java中构造和解析复杂的JSON对象

前言

博客首页:https://xiajiao.site

前往博客原文下载源码↓

博文链接:伊地知虾饺的博客-JSONX-在Java中构造和解析复杂的JSON对象

JSONX——JSON-Xiajiao,能够在Java中构造和解析复杂JSON对象的工具。

这两周在看vue和axios。前后端间数据的交互,常通过JSON来实现。学习过程中随手写了一个生成JSON格式的工具类,后面决定干脆写一个完备的类来在后端生成和解析JSON对象。

在JSONX之前,已有不少功能完备的、在Java中解析JSON对象的工具,比如GSON,FastJSON等。不过作为初学者,写一些简单的工具也是很好的锻炼,就当顺便复习JavaSE了。

开发用时三天。时间仓促,测试尚不完全,代码水平有限,因此JSONX在逻辑、性能和容错率上仍有很大的改进空间。

JSONX使用一个哈希表来存储所有JSON键值对。由于JSON中值的类型有对象、数组、字符串、数字和字面量,因此在哈希表中,键的类型是String,值的类型是Object

private Map<String, Object> map = new HashMap<>();

JSONX可以解析复杂的JSON字符串(包含嵌套的对象或数组)并将他们转化为正确的类。同时,JSONX也支持将JSON对象解析为POJO(Plain Ordinary Java Object)。

与JSON值的类型相对应,我将这些值解析为对应的Java中的对象:

  • JSON对象 -> Object

  • 数组 -> List<?>

  • 字符串 -> String

  • 数字 -> Java中对应的包装类

  • 字面量(true, false, null)-> Boolean对象中对应的值,null

需要注意的是,在解析JSON字符串时,JSONX统一将所有数组解析为List。因此,如果传入的POJO中有字段的类型是Java中的数组(如Integer[]),在解析为对应POJO对象时会抛出异常。

尽管如此,你依然可以在生成JSON字符串时使用Java中的数组(包括含有数组字段的POJO)。由于JSONX的哈希表中值的类型为Object,所有的数组必须为包装类的数组(如Integer[])而不能是基本类型数组(如int[])。

构造方法

JSONX提供以下五种构造方法:

public JSONX();
​
public JSONX(Map<String, Object> map);
​
public JSONX(String json, Class<?>... pojoClasses);
​
public JSONX(List<?> pojoList);
​
public JSONX(File file, Class<?>... pojoClasses);

JSONX()

显示声明的无参构造方法

JSONX(Map<String, Object> map)

通过一个构造好的哈希表创建JSONX对象,该哈希表将直接被赋给JSONX对象内的哈希表

JSONX(String json, Class<?>... pojoClass)

通过一个JSON字符串创建JSONX对象,该字符串将会被解析并将解析得到的键值对放入哈希表中。

如果你希望将JSON对象(无论是本身或是嵌套的)解析为POJO,请同时传递POJO的类对象。如果有多个可能的POJO对象,你需要同时传递他们的类对象。

JSONX(List<?> pojoList)

通过一个POJO的列表创建JSONX,通常在查询条目时使用。对于列表中的每一个POJO对象,JSONX会按照index:pojo 的格式将其放入JSON中,其中index为该POJO在列表中的索引(从0开始)

JSONX(File file, Class<?>... pojoClass)

通过一个本地的JSON文件创建JSONX对象。同样,如果有需要的话,你可以传递POJO的类对象。该构造方法可能会抛出IOException

API

Getter

JSONX提供以下几个Getter:

public Object get(String key);
​
public Set<String> getKeys();
​
public Collection<Object> getValues();
​
public Map<String, Object> getMap();
​
public Set<Map.Entry<String, Object>> getEntrySet();

JSONX不提供相应的Setter。在一个JSONX对象被创建后,其内部的值便是唯一的。

toString()

JSONX重写了toString()。当你调用JSONX对象的toString()时,会得到一个标准的JSON对象字符串。

静态方法(工具方法)

JSONX提供以下两个工具方法用来快捷地生成JSON字符串:

public static String getJSON(Object... para)
​
public static String getJSONXKeyValueSet(String key, Object value);

getJSON(Object... para)

快捷地生成JSON字符串。传递的参数应该按照“key, value, key, value...”的格式。如果传入的参数的个数为0或是为奇数,该方法会抛出IllegalArgumentException

getJSONXKeyValueSet(String key, Object value)

按照传入的参数快捷生成JSON键值对,该方法的返回值不包含左右两端的大括号

*其他private方法

JSONX中有一些不对用户开放的private方法。这些方法是构造或解析JSON字符串的辅助方法,也是JSONX的核心。如果你只是想使用JSONX,可以略过这一部分。

private void parseJSON(String json, Class<?>... pojoClass);
​
private static Object[] parseJSONValue(String json, int left, Class<?>... pojoClasses);
​
private static int findEndMark(String json, int left);
​
private static String getJSONXValue(Object value);

parseJSON(String json, Class<?>... pojoClass)

解析JSON字符串的方法。JSON字符串前后可以有空白字符,但trim()后的JSON字符串必须以{结束,以}结尾,否则,该方法会抛出IllegalArgumentException

此后,字符串会被“拆包”,即去除最外边的两个大括号,只保留中间的键值对,通过两个指针来进行解析。两个指针会依次取出每个键值对的键和值,并放入该JSONX对象的哈希表中。考虑到JSON对象值的复杂性,JSONX使用一个辅助方法parseJSONValue(String json, int left, Class<?>... pojoClasses)来解析JSON对象。

parseJSONValue(String json, int left, Class<?>... pojoClasses)

解析JSON字符串值的方法。参数中,json是原json字符串,不创建新子字符串的参数传递能提高性能。参数中的left是parseJSON中左指针的位置。在调用parseJSONValue前,parseJSON已将left的位置置于冒号后的第一个字符,即值的第一个字符处。

通过解析值的第一个字符,可以推测处出值的实际类型(对象,数组,字符串,数字,字面量)。这通过一组if-else语句来实现,因此效率并不太高。

该方法返回一个长度为2的Object数组。其中,数组的第一项是解析得到的JSON值的实际类型,第二项是在原JSON字符串中,该值之后的下一个键值对中键的第一个字符的索引。如果该值处于整个JSON字符串中最后一个键值对,第二项将是原JSON字符串的长度。在parseJSON方法中,这标志着解析的结束。

  • 当该JSON值为数组时(首字符为[),JSONX首先会创建一个List<Object>来容纳数组中的值。考虑到数组可能会有嵌套的情况,且JSON中数组的内容可能是JSON值的任意一种,JSONX首先会调用findEndMark找到与首字符相匹配的右括号来确定这个数组在JSON字符串中的位置。此后,JSONX会递归地调用parseJSONValue来解析数组中的每一个值。

  • 当该JSON值为对象时(首字符为{),JSONX同样会调用findEndMark来找到与首字符相匹配的右括号。此后,这个对象会从原JSON字符串中取出,当作 另一个JSON字符串(下文以subJSON代指)来处理。该方法基于subJSON创建一个JSONX对象。此后,对于参数传递的每一个POJO,JSONX会尝试将这个JSON对象与POJO对象相匹配。如果该JSON对象中每一个键都能与POJO中的一个字段相对应,JSONX就认为匹配。此后,JSONX会通过反射创建一个POJO类的对象,并直接通过反射中Field类提供的set方法设置该字段的值。由于使用反射,当JSON字符串中有较多对象时,性能可能会受到影响。如果所有的POJO都不匹配,JSONX就会直接将解析得到的哈希表作为解析得到的值。

  • 当该JSON对象为数字时,JSONX会首先判断数字后是否有,],来正确地取出代表该数字的字符串。对于该数字字符串,如果含有小数点,JSONX首先尝试调用Double.parseDouble(),将其解析为Double,如果抛出异常,则会将其解析为BigDecimal;如果不含小数点,JSONX会按照IntegerLongBigInteger的顺序依次解析,如果没有抛出异常,就直接将其作为解析得到的值。

  • 当该JSON对象为字符串或字面量时,JSONX会直接将对应的值放入哈希表。

findEndMark(String json, int left)

用来找到原JSON字符串中left处左括号对应右括号的辅助方法。该方法的平均时间复杂度为O(n),最坏情况(全部有嵌套的数组或嵌套的对象组成)下的时间复杂度为O(n ^ 2)。当JSON中含有大量的数组或对象(原有的或是嵌套的),性能便会降低。该方法的具体实现并不复杂:

private static int findEndMark(String json, int left) {
    char type = json.charAt(left);
    char endMark;
    switch (type) {
        case '{' -> endMark = '}';
        case '[' -> endMark = ']';
        default -> throw new RuntimeException("End mark of " + type + " is not defined");
    }
    //使用count来对嵌套的左括号计数,有多少个左括号,就有多少个右括号
    int count = 0;
    for (int i = left + 1; i < json.length(); i++) {
        char cur = json.charAt(i);
        if (cur == type) {
            count++;
        }
        else if (cur == endMark) {
            if (count == 0) return i;
            else count--;
        }
    }
    return -1;
}

getJSONXValue(Object value)

在将JSONX转为JSON字符串的辅助方法。由于JSON值类型的多样性,JSONX首先会判断该值的实际类型,并转换为正确的JSON字符串。

  • 如果value的实际类型为基本类型、基本类型的包装类或字符串,JSONX会直接生成正确的JSON值字符串。

  • 如果value的实际类型为数组或列表,JSONX会递归地调用该方法,以此得到正确的JSON值字符串。

  • 如果value的实际值为Map,JSONX会将该map中的键值对处理为一个JSON对象。

  • 如果value的实际值不满足以上几种情况,JSONX会将该value当作一个POJO类的实例,并通过反射取出其中所有字段和字段对应的值

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值