專案遇到的需求:程式接收來自外界的 JSON 資料,物件之各屬性內容以 KeyValuePair<string, string> 陣列儲存,序列化結果如下:

{
  "modType": [
    {
      "Key": "I",
      "Value": "獨立模組"
    },
    {
      "Key": "J",
      "Value": "聯合模組"
    }
  ],
 
  "source": [
    {
      "Key": "I",
      "Value": "內部"
    },
    {
      "Key": "E",
      "Value": "外部"
    }
  ],
 
  "statusOption": [
    {
      "Key": "0",
      "Value": "停止"
    },
    {
      "Key": "1",
      "Value": "運轉"
    },
    {
      "Key": "2",
      "Value": "暫停"
    }
  ]
}

若依此資料結構,JavaScript 前端 MVVM 繫結特定項目時要寫成 model.source[1].Value 不夠直覺,希望改成 model.source.E。JavaScript 要將 Key/Value 陣列轉成物件屬性不是難事,jQuery.each() 一行可以搞定:
var obj={};$.map(model.source,function(item){ obj[item.Key]=item.Value;});

不過,傳送繁瑣 JSON 資料到前端再簡化,自然不如在 C# 端直接轉換優雅,而這對 Json.NET 來說是小菜一碟。

程式範例附於下方,簡單說明原理:先將 JSON 字串用 JObject.Parse() 反序列化成 JObject 物件,透過 JObject.Properties() 可逐一取得象徵各屬性的 JProperty 物件,JProperty.Name 為屬性名稱,JProperty.Value 則為屬性值,在本案例為 Key/Value 物件組成的陣列。透過 p.Value as JArray 轉為 JArray 後可 foreach 取得陣列元素。陣列元件可視為包含 Key/Value 兩個屬性的 JObject,可透過 item["Key"]/item["Value"] 取值。我們為每個屬性建立一顆專屬 JObject,將 Key/Value 陣列以 propObj.Add(propName, propValue) 轉成 propObj 的一個個屬性值,藉以取代原有的 JArray,就完成了置換。其中有個小眉角,由於 Key 可能包含不合法的屬性名稱字元或格式(例如「.」字元或以數字起首),因此要藉由 Regex 取代及修正(數字起首時在前方加上「_」)。

static void Main(string[] args)
{
    var json = System.IO.File.ReadAllText("data.json");
    //將JSON轉為JObject
    JObject jo = JObject.Parse(json);
    //逐一轉換各屬性
    foreach (var p in jo.Properties())
    {
        //原本Key/Value陣列方式表達選項?容
        JArray a = p.Value as JArray;
        //準備一個新物件以屬性儲放選項
        JObject propObj = new JObject();
        //將{ "Key":"..", "Value":"..."}視為JObject
        foreach (JObject item in a)
        {
            string propName = (string)item["Key"]; //取出Key
            //將.換成_,數字起首時前方加_,避免產生無效屬性名
            propName =
                Regex.Replace(
                    //TODO:如有其他字元再擴充
                    Regex.Replace(propName, "[-.]", "_"),
                    "^[0-9]", //若以數字起始前方加_
                    m => "_" + m.Value);
            //取出Value
            string propValue = (string)item["Value"];
            //新增成屬性
            propObj.Add(propName, propValue);
        }
 
        jo[p.Name] = propObj;
    }
 
    Console.WriteLine(JsonConvert.SerializeObject(jo, Formatting.Indented));
    Console.Read();
}

轉換結果如下,成功!

{
  "modType": {
    "I": "獨立模組",
    "J": "聯合模組"
  },
  "source": {
    "I": "內部",
    "E": "外部"
  },
  "statusOption": {
    "_0": "停止",
    "_1": "運轉",
    "_2": "暫停"
  }
}

以上寫法展示完 JObject/JProperty/JArray 的概念與應用方式,接著我們來抄捷徑:

JObject jo = JObject.Parse(json);
foreach (var p in jo.Properties())
{
    p.Value =
        JObject.FromObject(
        (p.Value as JArray).Cast<JObject>()
        .ToDictionary(
            o => {
                string propName = (string)o["Key"];
                propName =
                    Regex.Replace(
                        Regex.Replace(propName, "[-.]", "_"),
                        "^[0-9]",
                        m => "_" + m.Value);
                return propName;
            }, 
            o => (string)o["Value"]));
}

JArray 經由 ToDictionary() 轉成 Dictionary<string, string>,呼叫 JObject.FromObject() 就直接轉成 JObject,收工!

後話:Json.NET 並不是轉換效能最好的 JSON 程式庫,但其完整性、成熟度與應用彈性實在沒話說,只想學一套 JSON 程式庫,選它就對了!

貼文後經網友提醒,補上之前寫過的另一篇介紹:使用dynamic簡化Json.NET JObject操作 兩帖併服,藥效加倍~ :P


Comments

Be the first to post a comment

Post a comment