鍍金池/ 問(wèn)答/Java  C#/ 用 C# 寫個(gè)方法解析簡(jiǎn)單的 JSON 字符串有哪些思路?

用 C# 寫個(gè)方法解析簡(jiǎn)單的 JSON 字符串有哪些思路?

我在 .NET 框架中沒有找到解析 JSON 字符串的簡(jiǎn)單方法,雖然有 Newtonsoft.Json 這種東西,但它太重,所以想自己造個(gè)輪子。

對(duì)于以下這種簡(jiǎn)單的 JSON 字符串:

{
   "error": {
      "code": "request_token_invalid", 
      "message": "The access token isn't valid."
   }
}

給定一個(gè)方法,傳入字符串和鍵,返回對(duì)應(yīng)的值。

public static string JsonToValue(string json,string key) {
    // Todo
}

我的思路是遍歷字符串,找到所有雙引號(hào)的位置,然后把這些字符串取出存入列表,位于 key 后面的字符串就是要找的值。

public static string JsonToValue(string json,string key) {
    var index = new List<int>();
    for (int i = 0; i < json.Length; i++) {
        if (json[i] == '"') index.Add(i);
    }
    var str = new List<string>();
    for (int i = 0; i < index.Count; i++) {
        str.Add(json.Substring(index[i] + 1,index[i + 1] - index[i] - 1));
        i++;
    }
    for (int i = 0; i < str.Count; i++) {
        if (str[i] == key) return str[i + 1];
    }
    return null;
}

但是這種方法太繁瑣了,大家還有沒有更好的思路?

回答
編輯回答
久舊酒

內(nèi)置方式:使用.NET Framework 3.5/4.0中提供的System.Web.Script.Serialization命名空間下的JavaScriptSerializer類進(jìn)行對(duì)象的序列化與反序列化,很直接。

Project p = new Project() { Input = "stone", Output = "gold" };
 JavaScriptSerializer serializer = new JavaScriptSerializer();
 var json = serializer.Serialize(p);
 Console.WriteLine(json);

 var p1 = serializer.Deserialize<Project>(json);
 Console.WriteLine(p1.Input + "=>" + p1.Output);
 Console.WriteLine(ReferenceEquals(p,p1));

契約方式:使用System.Runtime.Serialization.dll提供的DataContractJsonSerializer或者 JsonReaderWriterFactory實(shí)現(xiàn)。

Project p = new Project() { Input = "stone", Output = "gold" };
DataContractJsonSerializer serializer = new DataContractJsonSerializer(p.GetType());
string jsonText;

using (MemoryStream stream = new MemoryStream())
{
    serializer.WriteObject(stream, p);
    jsonText = Encoding.UTF8.GetString(stream.ToArray());
    Console.WriteLine(jsonText);
}

using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonText)))
{
    DataContractJsonSerializer serializer1 = new DataContractJsonSerializer(typeof(Project));
    Project p1 = (Project)serializer1.ReadObject(ms);
    Console.WriteLine(p1.Input + "=>" + p1.Output);
}
2018年5月31日 13:47
編輯回答
小眼睛
  1. 使用文法解析。(編譯原理)
  2. 使用現(xiàn)有輪子。
  3. 使用正則表達(dá)式。

第一種方法稍為復(fù)雜,需要使用一個(gè)類來(lái)處理,但是功能強(qiáng)大,可以解析列表和對(duì)象。
第二種方法可行,但不太滿足題主要求。
第三種方法結(jié)構(gòu)簡(jiǎn)單,只需要一個(gè)函數(shù),但是只能解析字符串鍵值對(duì)。通過(guò)恰當(dāng)?shù)奶幚砜梢越馕鰯?shù)字和布爾值。

下面詳細(xì)說(shuō)一下這三種方法。

第一種方法

需要一個(gè)類來(lái)代表解析器,再用兩個(gè)類表示對(duì)象。代碼稍長(zhǎng),共有 300 行左右,若題主需要,我再貼上來(lái)。

第二種方法

Microsoft.Extensions.Configuration.Json,文檔:Configuration in ASP.NET Core

應(yīng)該能夠滿足需求。

第三種方法

public static string JsonToValue(string json, string key)
{
    var newLine = Environment.NewLine;
    var pattern =
        $"(?:\"{key}\"|'{key}')                                 # 匹配鍵" + newLine +
        @"\s*:\s*                                               # 匹配冒號(hào)" + newLine +
        "(?:\"(?<double>[\\s\\S]*?)\"|'(?<single>[\\s\\S]*?)')  # 匹配值" + newLine +
        "\\s*(?:,\\s*(?:\"|')|\\})                              # 匹配結(jié)尾";
    var result = Regex.Match(json, pattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
    if (result.Success)
    {
        if (result.Groups["double"].Success)
        {
            return result.Groups["double"].Value;
        }
        else
        {
            return result.Groups["single"].Value;
        }
    }
    return null;
}

正則表達(dá)式文檔

首先考慮需要匹配的內(nèi)容:鍵和值。如果不考慮轉(zhuǎn)義字符,可以簡(jiǎn)單地使用 "{ key }" 進(jìn)行匹配,考慮到可以使用單引號(hào)作為字符串地標(biāo)識(shí),還要加上 '{ key }'。

鍵值對(duì)之間是一個(gè)冒號(hào),使用 \s*:\s* 進(jìn)行匹配。

再之后則匹配值,由于可以在值字符串中出現(xiàn)轉(zhuǎn)義字符,所以理論上在值字符串中可以出現(xiàn)任何字符,使用 [\s\S]* 進(jìn)行匹配。防止匹配長(zhǎng)度過(guò)長(zhǎng),需要使用非貪婪匹配,得到 "([\s\S]*?)",同樣地,還要另外再匹配單引號(hào)的格式。為了及時(shí)終止,還需要匹配值之后的內(nèi)容。

接著考慮值之后的內(nèi)容,如果這個(gè)鍵值對(duì)之后還有鍵值對(duì),那么后面跟的是 ,,否則,這個(gè)鍵值對(duì)是對(duì)象的最后一個(gè)鍵值對(duì),后跟 }。但是,還有一種極端情況,如 "key": "\"Good.\", he said.,在這里,雖然 " 后跟 ,,但是值并沒有結(jié)束,所以應(yīng)該繼續(xù)往后加內(nèi)容。典型的值后面的內(nèi)容應(yīng)該是 , ",當(dāng)然還要考慮單引號(hào)。

因此可得最終的正則表達(dá)式。

總結(jié)使用正則表達(dá)式的缺點(diǎn)如下:

  1. 不能得到對(duì)象和數(shù)組。
  2. 不能檢查 Json 格式是否正確。

在我的實(shí)現(xiàn)中還有以下缺點(diǎn):

  1. 不能匹配鍵對(duì)應(yīng)的第二個(gè)值,如數(shù)組中的一系列對(duì)象。
  2. 沒有轉(zhuǎn)換轉(zhuǎn)義字符。

結(jié)果:

static void Main(string[] args)
{
    var jsonSegments = new string[]
    {
        "{",
        "  \"a\": {",
        "    \"b\": true",
        "  },",
        "  \"c\": [",
        "    1,",
        "    2",
        "  ]",
        "  \"d\": 1,",
        "  \"e\": \"\\\"Good.\\\", he said.\",",
        "  \'e': 'second e',",
        "  'f': 'get f'",
        "}"
    };
    var json = string.Join(Environment.NewLine, jsonSegments);
    WriteLine(json);
    WriteLine();

    Show(json);
}

static void Show(string json)
{
    foreach (var key in new string[] { "a", "b", "c", "d", "e", "f" })
    {
        WriteLine(key + ": " + (JsonToValue(json, key) ?? "not found."));
    }
}

輸出:

{
  "a": {
    "b": true
  },
  "c": [
    1,
    2
  ]
  "d": 1,
  "e": "\"Good.\", he said.",
  'e': 'second e',
  'f': 'get f'
}

a: not found.
b: not found.
c: not found.
d: not found.
e: \"Good.\", he said.
f: get f

總結(jié)

使用文法解析更優(yōu)雅有趣一些,且功能更強(qiáng)大。使用正則雖然簡(jiǎn)單,但是設(shè)計(jì)正則匹配模式的過(guò)程很漫長(zhǎng),絲毫不比文法解析快。正則匹配還是不太合適。

望采納。

2018年9月18日 14:21
編輯回答
臭榴蓮

就是編譯原理這一套,先用正則表達(dá)式做詞法分析,然后用自頂向下/自底向上的語(yǔ)法分析,雖然有點(diǎn)殺雞用牛刀,但造輪子嘛,不就是為了搞這些學(xué)術(shù)性強(qiáng)一點(diǎn)的東西

2017年3月4日 17:25