從Excel到Power Query:函數實戰系列(6)_細說Power Query的內建函數(以匯入json檔案為例)

另一種最常見也非常普及的資料檔案格式為JSON檔案。其特性是具備層次化結構,非常適合儲存複雜和巢狀式(Nest)的資料(巢狀一詞在簡體字的翻譯中譯為嵌套)。例外,此種檔案格式可讀性好,靈活且支援動態資料,因此常被運用於API回應和資料交換。與簡單的csv格式相比,json的資料可以巢狀,適合多層次的關係型結構,但也因此需要使用解析器才能讀取和匯入(當然,Power Query已具有解析能力)。

匯入這類型的檔案格式時必須驗證JSON的結構合法性(避免語法錯誤),並適度展開或壓縮巢狀結構以便處理。而擷取所需欄位時也需要設計解析邏輯,以避免遺漏關鍵資料。如下便是典型的json檔案格式:

在筆者另一系列的文章與實作:學習Json檔案格式與實作系列,便有針對JSON的檔案格式,有著詳細的解釋與說明,若興趣可以前去一探究竟與指教。若覺得有緣也值得,亦不吝分享給您的朋友。

Json.Document()函數

在此範例中透過Power Query操作匯入JSON檔案時,會執行3個操作步驟:

其中,第1個操作步驟便是Json.Document()函數。

Json.Document( )也是Power Query的內建函數,其目的是用於解析JSON格式的資料,主要功能是將JSON字串轉換為Power Query可處理的資料結構,例如記錄(Record)或列表(List)。如同前例介紹的Csv.Document()函數,此函數裡的第1個參數也是描述要讀取的Json檔案之完整路徑與檔案名稱,所以在此也常搭配另一個內建函數File.Contents( )。而此Json.Document( )函數的標準語法為:

第2個參數是可選用的encoding,用來指定編碼類型,例如 UTF-8。如果未提供,系統會使用預設編碼。

Table.FromList()函數

在匯入Json檔案後,便藉由第2個查詢步驟Table.FromList ()函數,將清單(List)轉換成資料表(Table)。此函數非常靈活,允許將指定的清單配合其他參數來控制轉換成資料表。其標準語法與參數如下:

參數說明:

  • List(清單):這是必需的參數,表示要轉換的清單。
  • splitter(分隔符號):這是選用的參數。用來定義如何將清單中的元素分割為多個資料行。若未指定此參數,清單會被視為單一資料行。專職於將清單中的每個元素分割成多個值,而這些值即成為資料表的資料行。若未指定此參數,或者傳遞null,則清單中的每個元素將會成為資料表中單一資料行的值。而在實務的應用上,通常此參數是一個函數,透過Power Query提供的內建分割函數來進行清單資料的轉換,例如:
    • Splitter.SplitTextByDelimiter(delimiter as text, optional quotes as nullable text) as function函數,這是根據指定的分隔符號分割文字。
    • Splitter.SplitTextByLengths(lengths as list) as function函數。這是根據指定的長度來分割文字。
    • Splitter.SplitTextByPositions(positions as list) as function函數。這是根據指定的起始位置分割文字。
    • Splitter.SplitTextByWhitespace() as function函數。這是根據空白字元來分割文字。
    • Splitter.SplitByNothing()函數。代表不分割,也就是保留輸入本文的完整性,不進行拆分,在需要保留原始值的操作中,這是非常有用的。
      當然,使用者也可以自訂分割函數。
  • columns(資料行名稱):這是選用的參數。而此參數有兩種型式設:
    • 數值型式,可用來指定資料表的資料行數量。如果splitter參數產生比此數值少的資料行,則會使用default參數所指定的值來填充剩餘的資料行。如果 splitter產生的資料行數量超過此數值,則會忽略多餘的資料行。
    • 清單型式,即資料行名稱清單: 來指定資料表的資料行名稱,通常這是一個文字清單(諸如 {"Column1", "Column2"})。清單中的每個文字對應資料表中的一個資料行。如果splitter函數產生的資料行數量與此清單中的名稱數量不符,則會根據情況填充預設值或忽略多餘的資料行。
  • default(預設值):這是選用的參數。定義當清單中有空值時應插入的預設值。而此參數只有在splitter參數產生比columns參數指定的數量少的資料行時才有效。可用來指定填充缺失資料行的預設值。若省略此參數,則缺失的資料行將會填充null值
  • ExtraValuesMethod(額外值):這也是選用的參數,正如其名可翻譯為額外值的處理方式,也就是透過一個整數值來指定如何處理分割後產生的額外值。此參數是根據當splitter函數分割清單中的一個元素後,產生的值若比columns參數指定的資料行數量還要多時,應該如何處理這些額外的值。它接受以下的設定值作為選項:
    • 0 (或 ExtraValues.List): 將額外的值作為一個清單置入最後一個資料行中。這也是預設值。
    • 1 (或 ExtraValues.Error): 如果有額外的值產生,則引發錯誤。
    • 2 (或 ExtraValues.Ignore): 忽略額外的值。

看到這裡,許多初學者可能還是一眼茫然,但稍後的實作範例將特別為大家解說這一參數的設定與運用。

我們先來看看一個簡單的例子。譬如下列是一個清單(List)資料,內含3個字串元素:

來源 = {"8192,65536", "全泉科技有限公司", "資訊傳播,數據分析"}

若針對上述來源,進行如下清單轉換為資料表的函數撰寫:

輸出 = Table.FromList(

    {來源, Splitter.SplitTextByDelimiter(","), {"Column A", "Column B"}, "無資料")

則輸出的查詢結果便是資料行名稱分別為Column A與Column B且包含3筆資料列的資料表,原本清單裡的元素,轉換成一筆筆的資料列,而元素裡有缺失的資料則填充了字串"無資料"。

另外,在此以另外一個清單範例介紹一下最後一個參數ExtraValues.List的運用。譬如:若有一資料來源清單內含3個字串元素,資料如下:

來源 ={"陳小明,30,台北市,中華民國", "凱西,25,倫敦", "Peter,40,Paris,France,Europe"},

此清單內含3個字串元素,第1個字串元素有4個以逗點分隔的內容;第2個字串元素有3個以逗點分隔的內容;第3個字串元素有5個以逗點分隔的內容。如果資料表準備輸出3個資料行:姓名、年齡、城市,那麼除了含3個資料內容的第2個字串元素外,其他字串元素的多餘資料內容(即額外的資料內容)要如何處理就很重要了,這也正是發揮最後一個參數ExtraValuesMethod的時候了。

如下所示:

輸出 = Table.FromList(來源,Splitter.SplitTextByDelimiter(","),{"姓名", 年齡", "城市"},null,0)

當最後一個參數ExtraValuesMethod設定為0時,超過3個資料行(姓名、年齡、城市)的其他資料行其內容將與最後一個資料行(此例為城市資料行)的內容,以清單(List)的資料結構呈現在最後一個資料行裡。

若ExtraValuesMethod = 1 ,則只有當分割後產生的值比指定的資料行數量多時才會引發錯誤。根據前述的程式碼,我們指定3個資料行(姓名、年齡、城市)的輸出,而來源清單裡的第1個元素在分割後產生4個資料值,多於指定的3個資料行,因此會引發錯誤;第3個元素在分割後產生5個資料值,亦多於指定的3個資料行,因此也會產生錯誤;只有第2個元素在分割後產生3個資料值,等於指定的3個資料行,因此不會引發錯誤,資料會正常顯示。

當ExtraValuesMethod = 2時,清單裡各個元素在分割資料後若產生的資料值超過指定的3個資料行,多餘的額外值都將忽略。因此,僅顯示3個資料行的內容。

從上述的範例可以了解清單轉換成資料表的靈活與彈性,也由此可見在Power Query的環境下,匯入以清單為結構的JSON檔案格式時,清單轉換為資料表的步驟是何等的重要。

清單的展開有以下兩種情境:

  • 「展開至新資料列」(Expand to New Rows)
    這種情境是將清單中的每個元素拆分成獨立的資料列,這種情境非常適合在多對一關係的展開。在Power Query中,是運用Table.ExpandListColumn()函數來達成。例如:
    = Table.ExpandListColumn(輸出, "City")
  • 「擷取值」(Extract Values)
    這種情境是將清單內的所有元素合併成單一文字欄位,而元素與元素之間可以指定分隔符號(譬如逗號)來串接元素,這種情境非常適合需要保持欄位精簡但又要顯示所有資訊的狀況。在Power Query中,可以運用= Table.TransformColumns()函數完成。例如:
    = Table.TransformColumns(輸出, {"City", each Text.Combine(List.Transform(_, Text.From), ","), type text})