FastJSON解析Json字串(反序列化為List、Map)

Frenkie發表於2022-02-11

在日常開發與資料庫打交道的時候,常有以Json格式的字串儲存到資料庫的情況,當在Java程式中獲取到對應的Json格式的String字串後,如何才能轉換為我們想要的資料格式(比如轉換成Java中的自定義類等),就需要做出對Json資料解析的,而我最近寫的介面就遇到了這樣的需求,我藉助阿里的Fastjson api實現json轉化為Java POJO,現在進行簡單的總結,記錄一下。

配置maven依賴

分別引入三個依賴,分別是fastjson、lombok、commons工具包。

<dependencies>
    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.78</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.22</version>
        <scope>compile</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.12.0</version>
    </dependency>
</dependencies>

其中版本號我已經寫上了,如有需要請使用特定版本,建議使用最新版。

資料準備

下面就簡單構造一點資料,對資料解析的要求:整個json轉化為一個POJO,其中 code、name型別為String,schedule型別為List<String>表示計劃清單,completion型別為Map<String,String>表示完成情況一一對映。

構建週一到週日的 計劃&完成情況 的json資料:

[
    {
        "code":"monday",
        "name":"週一",
        "schedule":"get_up_9am,exercise_one_hour,study_one_hour,goto_bed_before_12pm",
        "completion":"get_up_9am=y,exercise_one_hour=n,study_one_hour=y,goto_bed_before_12pm=n"
    },{
        "code":"tuesday",
        "name":"週二",
        "schedule":"exercise_one_hour,study_one_hour,goto_bed_before_12pm",
        "completion":"exercise_one_hour=y,study_one_hour=y,goto_bed_before_12pm=y"
    },{
        "code":"wednesday",
        "name":"週三",
        "schedule":"get_up_9am,exercise_one_hour,goto_bed_before_12pm",
        "completion":"get_up_9am=n,exercise_one_hour=n,goto_bed_before_12pm=n"
    },{
        "code":"thursday",
        "name":"週四",
        "schedule":"",
        "completion":""
    },{
        "code":"friday",
        "name":"週五",
        "schedule":"study_one_hour,goto_bed_before_12pm",
        "completion":"study_one_hour=n,goto_bed_before_12pm=n"
    },{
        "code":"saturday",
        "name":"週六",
        "schedule":"goto_bed_before_12pm",
        "completion":"goto_bed_before_12pm=n"
    },{
        "code":"sunday",
        "name":"週日",
        "schedule":"",
        "completion":""
    }
]

JSON格式字串轉Java物件

下面就是直接貼程式碼

DO&DTO

DO是全部為String型別的資料,DTO是其中的schedule為List,completion為Map格式的

WeekScheduleDO

package com.xxx;
import lombok.Data;

@Data
public class WeekScheduleDO {
    private String code;
    private String name;
    private String schedule;
    private String completion;
}

WeekScheduleDTO

package com.fast;
import lombok.Data;
import java.util.List;
import java.util.Map;

@Data
public class WeekScheduleDTO {
    private String code;
    private String name;
    private List<String> schedule;
    private Map<String,String> completion;
}

SelfJSONUtils

把解析需要用到的方法自定義封裝一下

package com.xxx;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

public class SelfJSONUtils {
    public static List<WeekScheduleDTO> toWeekScheduleDOs(String str){
        // String->List<WeekScheduleDO>
        List<WeekScheduleDO> weekScheduleDOs = JSON.parseObject(str, new TypeReference<List<WeekScheduleDO>>() {});

        List<WeekScheduleDTO> result = new ArrayList<>();
        for (WeekScheduleDO item:weekScheduleDOs) {
            result.add(toWeekScheduleDTO(item));
        }

        return result;
    }

    private static WeekScheduleDTO toWeekScheduleDTO(WeekScheduleDO item){
        WeekScheduleDTO weekScheduleDTO = new WeekScheduleDTO();
        weekScheduleDTO.setCode(item.getCode());
        weekScheduleDTO.setName(item.getName());

        // "schedule":"get_up_9am,..." 轉換成lsit
        String[] schedule = item.getSchedule().split(",");
        weekScheduleDTO.setSchedule(Arrays.stream(schedule).filter(e -> StringUtils.isNoneBlank(e)).collect(Collectors.toList()));

        // "completion":"get_up_9am=y,..." 轉換成map
        weekScheduleDTO.setCompletion(toMap(item.getCompletion().split(",")));

        return weekScheduleDTO;
    }

    private static Map<String,String> toMap(String[] args){
        Map<String,String> map = new HashMap<>();
        for(String arg : args){
            if(!arg.isEmpty()) {
                String[] split1 = arg.split("=");
                map.put(split1[0], split1[1]);
            }
        }
        return map;
    }
}

測試&呼叫

package com.xxx;
import java.util.List;

public class Demo {
    public static void main(String[] args) {
        String json = "[{\"code\":\"monday\",\"name\":\"週一\",\"schedule\":\"get_up_9am,exercise_one_hour,study_one_hour,goto_bed_before_12pm\",\"completion\":\"get_up_9am=y,exercise_one_hour=n,study_one_hour=y,goto_bed_before_12pm=n\"},{\"code\":\"tuesday\",\"name\":\"週二\",\"schedule\":\"exercise_one_hour,study_one_hour,goto_bed_before_12pm\",\"completion\":\"exercise_one_hour=y,study_one_hour=y,goto_bed_before_12pm=y\"},{\"code\":\"wednesday\",\"name\":\"週三\",\"schedule\":\"get_up_9am,exercise_one_hour,goto_bed_before_12pm\",\"completion\":\"get_up_9am=n,exercise_one_hour=n,goto_bed_before_12pm=n\"},{\"code\":\"thursday\",\"name\":\"週四\",\"schedule\":\"\",\"completion\":\"\"},{\"code\":\"friday\",\"name\":\"週五\",\"schedule\":\"study_one_hour,goto_bed_before_12pm\",\"completion\":\"study_one_hour=n,goto_bed_before_12pm=n\"},{\"code\":\"saturday\",\"name\":\"週六\",\"schedule\":\"goto_bed_before_12pm\",\"completion\":\"goto_bed_before_12pm=n\"},{\"code\":\"sunday\",\"name\":\"週日\",\"schedule\":\"\",\"completion\":\"\"}]";
        
        List<WeekScheduleDTO> weekScheduleDTOS = SelfJSONUtils.toWeekScheduleDOs(json);

        System.out.println(weekScheduleDTOS);
    }
}

現在就可以直接執行或debug檢視解析情況

這一個需求應該算常規復雜的情況,如果還有更為複雜的情況,轉換形式也可以依葫蘆畫瓢整出來~

注意的點

如果你細心的話,你就會發現我的程式碼有一處做出了冗餘的操作,它位於SelfJSONUtils#toBatchItemDTO的轉換為List那裡的Stream流處

weekScheduleDTO.setSchedule(Arrays.stream(schedule).filter(e -> StringUtils.isNoneBlank(e)).collect(Collectors.toList()));

fastjson轉換的時候,預設會把空的資料剔除掉,根據這一特性,我直接使用下面程式碼就行了(沒有filter):

weekScheduleDTO.setSchedule(Arrays.stream(schedule).collect(Collectors.toList()));

其實不加 filter(e -> StringUtils.isNoneBlank(e)) 是不行的。在程式碼中的map轉換的時候,確實是把空的資料剔除掉了,轉map出來以後確實size=0(如下圖),在轉List的時候,不過來空資料的話,該資料就會以""的形式儲存在List中(這個坑我半天!!)

除了上面使用 StringUtils#isNoneBlank 除空以外,還可以使用 e.hashCode()!=0 來解決,當然這是在沒有StringUtils可用的情況下使用的策略

weekScheduleDTO.setSchedule(Arrays.stream(schedule).filter(e -> e.hashCode()!=0).collect(Collectors.toList()));

簡單分析一下出現這種情況產生的原因:

fastjosn解析josn為POJO的時候,先會建立一個pojo空物件,之後通過實體類的set方法對引數進行賦值,由於fastjson解析時預設把為null的資料剔除掉了,所以fastjson沒保留下的欄位就不會進行set操作,而pojo類這個欄位自始至終都沒有被誰賦值,作為String型別就預設為"",而在轉換為List的時候,就會直接儲存進一個""(List可以儲存多個"")。

這一個問題也許並不大,但是當業務需求就是因為這一個小點不能按照我們的思路去執行的時候,就很難找出這一個微小的問題。

另外,JSON資料反序列化為Java物件時,必須要有預設無參的建構函式,否則會報如下異常

com.alibaba.fastjson.JSONException: default constructor not found.

Fastjson API

Fastjson被公認為Java與json互動最快的庫!簡單看了一下Fastjson的api設計,對於序列化和發序列化json資料很多的可選操作,比如 @JSONField 、BeanToArray、ContextValueFilter 配置 JSON 轉換、NameFilter 和 SerializeConfig等等。具體使用查閱官方API即可。


參考:

JSON一文通透,Java、JS與JSON互轉

Fastjson 簡明教程 | 菜鳥教程 (runoob.com)

相關文章