解析XML和JSON內容的一點技巧

解析XML和JSON內容的一點技巧

概述

在沒有統一標準的狀況下,一個系統對接多個外部系統每每會遇到請求接口響應數據異構的狀況,有可能返回的是XML,也有可能返回
JSON。除了返回類型不一樣,內容結構也不盡相同。以XML類型爲例,
接口1返回內容javascript

<root>
    <bizKey>16112638767472747178067</bizKey>
    <returnMsg>OK</returnMsg>
    <returnCode>200</returnCode>
    ...
</root>

接口2返回內容java

<root>
    <bid>16112638767472747178068</bid>
    <note>成功</note>
    <returnStatus>1</returnStatus>
    ...
</root>

若是在咱們系統中爲每種格式的內容針對處理顯然是不合理的,上面的內容中咱們只是關心三種信息,分別是業務ID、狀態值和描述信息,那麼可不能夠抽象這三種信息,
得到這些信息後再進行業務邏輯處理。node

解析XML和JSON

根據業務抽象咱們須要從XML或者JSON內容中得到三種信息,咱們這裏將會使用XPath和JSONPath的方式來解析。好比得到接口1的重要信息,
咱們能夠設定三個XPath表達式,git

{
    bid: "/root/bizKey",
    code: "/root/returnCode",
    description: "/root/returnMsg"
}

bid,codedescription對應咱們系統本身定義的字段名。
解析JSON內容也是同理的,只不過定義的是JSONPath表達式。github

分兩步走處理數據內容

假設咱們從原始的XML和JSON數據中得到了bid,codedescription信息,
從接口1得到express

{
    bid: '16112638767472747178067',
    code: '200',
    description: 'OK'
}

從接口2得到緩存

{
    bid: '16112638767472747178068',
    code: '1',
    description: '成功'
}

假設咱們從接口1文檔獲知狀態值200表示請求成功,從接口2文檔獲知狀態值1表示請求成功,雖然他們都表示請求成功,可是咱們仍是不能
把他們原本來本地保存到咱們的業務相關表中(固然這些響應數據仍是須要保存到另外的記錄表中的,至少方便排查問題)。
假設咱們的業務相關表是這樣設計的函數

字段名 類型 描述
bid string 業務ID
code int 狀態值,0=初始,1=請求中,2=成功,3=失敗
description string 描述

所以,咱們還必須定義規則把接口1返回的狀態值200轉換爲咱們系統的2,把接口2返回的狀態值1轉換爲咱們系統的2
總結一下,兩步走解析XML和JSON數據內容工具

  1. 根據XPath或者JSONPath表達式解析得到重要信息
  2. 根據規則轉換狀態值

第一步解析數據得到重要信息

以XML爲例,性能

public class XmlParseUtils {
    private DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
    private XPathFactory xpathFactory = XPathFactory.newInstance();
    
    /**
     * 
     * @param param    數據內容
     * @param paths 表達式
     * @return
     * @throws Exception
     */
    public Map<String,Object> parse(String param, Map<String,String> paths) throws Exception{
        InputSource inputSource = new InputSource(new StringReader(param));
        Document document = dbFactory.newDocumentBuilder().parse(inputSource);
        Map<String,Object> map = Maps.newHashMap();
        for(String key : paths.keySet()) {
            XPath xpath = xpathFactory.newXPath();
            Node node = (Node) xpath.evaluate(paths.get(key), document, XPathConstants.NODE);
            if(node == null) {
                throw new Exception("node not found, xpath is " + paths.get(key));
            }
            map.put(key, node.getTextContent());
        }
        return map;
    }

}

parse函數的返回類型也能夠是Map<String,String>,暫且用Map<String,Object>

第二步根據規則轉換狀態值

這一步稍稍有點麻煩,不過咱們先不考慮代碼實現,反正你能想到的可能別人已經幫你實現了。首先咱們根據接口文檔定義規則,寫出規則表達式(或者其餘的什麼),
又是表達式。假設接口1的返回的狀態值比較簡單,只有200表示成功,其餘狀況都是失敗,那麼咱們能夠這樣定義規則,

code.equals("200") ? 2: 3

或者

<#if code == "200">
2
<#else>
3
<#/if>

亦或者

function handle(arg) {
    if(arg == 200) {
        return 2;
    }
    return 3;
}
handle(${code})

以上根據同一份文檔定義了三種不一樣類型的狀態值轉換規則,確定須要三種不一樣的實現。下面一一說明,

三目表達式

code.equals("200") ? 2: 3是一個三目表達式,咱們將使用jexl引擎來解析,利用第一步解析數據得到重要信息的結果,咱們能夠這樣作

public Object evaluateByJexl(String expression, Map<String,Object> context) {
        JexlEngine jexl = new JexlBuilder().create();
        JexlExpression e = jexl.createExpression(expression);
        JexlContext jc = new MapContext(context);
        return e.evaluate(jc);
    }

FreeMarker模板

<#if code == "200">
2
<#else>
3
<#/if>

處理這段模板咱們能夠這麼作

/**
     * 
     * @param param FreeMarker模板
     * @param context
     * @return
     * @throws Exception
     */
    public String render(String param, Map<String,Object> context) throws Exception {
        Configuration cfg = new Configuration();
        StringTemplateLoader stringLoader = new StringTemplateLoader();
        stringLoader.putTemplate("myTemplate",param);
        cfg.setTemplateLoader(stringLoader);
        Template template = cfg.getTemplate("myTemplate","utf-8");
        StringWriter writer = new StringWriter();
        template.process(context, writer);
        return writer.toString();
    }

若是FreeMarker模板比較複雜,從模板預編譯成Template可能會消耗更多的性能,就要考慮把Template緩存起來。

JavaScript代碼段

function handle(arg) {
    if(arg == 200) {
        return 2;
    }
    return 3;
}
handle(${code})

這段js代碼中存在${code},首先它須要使用FreeMarker渲染獲得真正的handle方法的調用參數,而後

public Object evaluate(String expression) throws Exception {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("javascript");
        return engine.eval(expression);
    }

ScriptEngineManager的性能估計不太樂觀,畢竟是一個語言的引擎。

不一樣轉換規則實現的比較

類型 實現 優勢 缺點
三目表達式 Jexl 簡單(easy) 簡單(simple)
FreeMarker模板 FreeMarker -- --
JavaScript代碼段 FreeMarker + ScriptEngine 直觀 過程複雜,性能問題

看起來Freemarker是一個不錯的選擇。
至此兩步走小技巧已經實現了,都是利用了現成的代碼實現。

或許咱們會這樣的挑戰,在作狀態值轉換時須要知道當前系統某個業務狀態值的狀況,
此時Freemarker表達式多是這樣的,

<# assign lastCode = GetLastCode(code)>
<#if lastCode == "2">
2
<#elseif code == "200">
2
<#else>
3
<#/if>

這裏咱們可使用Freemarker的特性,自定義Java函數或工具類,在模板中調用。

代碼地址

https://github.com/Honwhy/xml...

相關文章
相關標籤/搜索