從檔案萃取文字部分建立索引是全文檢索的必要程序,先前介紹過為 PDF、Office 檔案產生文字索引的做法,實際開發則遇到為 JSON 建立文字索引的需求。借用上回遞迴文章的組織資料當實例,假設 JSON 格式如下:
{
"Name": "總經理",
"Children": [
{
"Name": "行政部",
"Children": [
{ "Name": "人資組" },
{ "Name": "總務組" }
]
},
{
"Name": "資訊部",
"Children": [
{ "Name": "網路組" },
{ "Name": "研發組" }
]
},
{
"Name": "業務部",
"Children": [
{
"Name": "海外組",
"Children": [
{ "Name": "海外一科" },
{ "Name": "海外二科" }
]
},
{
"Name": "通路組",
"Children": [
{ "Name": "行銷科" },
{ "Name": "電銷科" }
]
}
]
}
]
}
其中 Name、Children 是屬性名稱而非內容本體,不應納入搜尋範圍,故建索引時只需提取「總經理、行政部、人資組、總務組...」等純部門名稱就好。理論上應該找得到現成的 IFilter 或程式庫,但感覺原理不難,與其花時間尋找,不如自已寫個「JSON 文字資料抽取函式」練練功也好。(對啦對啦,我就是手癢愛亂寫啦,來咬我呀)
最早的想法是採遞迴 (或是上回的 Stack<T> + yield 奇技) 巡遍每一個 Property 讀取成字串。遇到物件就探索所有屬性、遇到陣列就跑完每一個元素,層層遞迴抓出所有內容。試寫了一陣子,閃過一個好點子,何不把 JSON 轉成 XML 再讀取 InnerText?秒殺搞定!!
Json.NET 果然不負所望,內建的 JsonConvert.DeserializeXmlNode Method (String, String)方法可將 JSON 字串直接轉成 XmlNode,得來全不費功夫~
staticstring ExtractJsonText(string raw)
{
var node = JsonConvert.DeserializeXmlNode(raw, "Root");
return node.InnerText;
}
staticvoid Main(string[] args)
{
var json = System.IO.File.ReadAllText("Org.json");
Console.WriteLine(ExtractJsonText(json));
Console.Read();
}
簡單幾行程式得到結果如下:
總經理行政部人資組總務組資訊部網路組研發組業務部海外組海外一科海外二科通路組行銷科電銷科
但有個問題。文字是提取出來了,卻全黏在一起,不利於分詞器正確切詞。有個簡單的改善做法設法在各屬性值間插入空白,我想到改用 InnerXml 加 Regex 置換 XML 標籤的做法可以實現。另外還有個狀況是 DeserializeXmlNode() 會將整個 JSON 視為一整個 Object,必須以 { 開頭 } 結尾,遇到 JSON 以 [、] 開頭及結尾會出錯,但這也難不倒我,偵測到主體是陣列時,外面再套一層 { "Array": [ … ] } 即可搞定。
改良版本如下:
staticstring ExtractJsonText(string raw)
{
var json = raw.TrimStart(' ', '\t', '\r', '\n');
if (json.StartsWith("["))
json = $@"{{ ""Array"": {json} }}";
var node = JsonConvert.DeserializeXmlNode(json, "Root");
return//將Element Tag換成空白,再去除連續空白
string.Join(" ",
Regex.Replace(node.InnerXml, "<[^>]+>", " ")
.Split(newchar[] {' '}, StringSplitOptions.RemoveEmptyEntries)
.ToArray());
}
執行結果:
總經理 行政部 人資組 總務組 資訊部 網路組 研發組 業務部 海外組 海外一科 海外二科 通路組 行銷科 電銷科
完美! Json.NET 好威啊~