用 C# 写个方法解析简单的 JSON 字符串有哪些思路?

我在 .NET 框架中没有找到解析 JSON 字符串的简单方法,虽然有 Newtonsoft.Json 这种东西,但它太重,所以想自己造个轮子。

对于以下这种简单的 JSON 字符串:

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

给定一个方法,传入字符串和键,返回对应的值。

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

我的思路是遍历字符串,找到所有双引号的位置,然后把这些字符串取出存入列表,位于 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;
}

但是这种方法太繁琐了,大家还有没有更好的思路?

阅读 3.6k
3 个回答

内置方式:使用.NET Framework 3.5/4.0中提供的System.Web.Script.Serialization命名空间下的JavaScriptSerializer类进行对象的序列化与反序列化,很直接。

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实现。

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);
}

就是编译原理这一套,先用正则表达式做词法分析,然后用自顶向下/自底向上的语法分析,虽然有点杀鸡用牛刀,但造轮子嘛,不就是为了搞这些学术性强一点的东西

  1. 使用文法解析。(编译原理)
  2. 使用现有轮子。
  3. 使用正则表达式。

第一种方法稍为复杂,需要使用一个类来处理,但是功能强大,可以解析列表和对象。
第二种方法可行,但不太满足题主要求。
第三种方法结构简单,只需要一个函数,但是只能解析字符串键值对。通过恰当的处理可以解析数字和布尔值。

下面详细说一下这三种方法。

第一种方法

需要一个类来代表解析器,再用两个类表示对象。代码稍长,共有 300 行左右,若题主需要,我再贴上来。

第二种方法

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

应该能够满足需求。

第三种方法

public static string JsonToValue(string json, string key)
{
    var newLine = Environment.NewLine;
    var pattern =
        $"(?:\"{key}\"|'{key}')                                 # 匹配键" + newLine +
        @"\s*:\s*                                               # 匹配冒号" + newLine +
        "(?:\"(?<double>[\\s\\S]*?)\"|'(?<single>[\\s\\S]*?)')  # 匹配值" + newLine +
        "\\s*(?:,\\s*(?:\"|')|\\})                              # 匹配结尾";
    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;
}

正则表达式文档

首先考虑需要匹配的内容:键和值。如果不考虑转义字符,可以简单地使用 "{ key }" 进行匹配,考虑到可以使用单引号作为字符串地标识,还要加上 '{ key }'

键值对之间是一个冒号,使用 \s*:\s* 进行匹配。

再之后则匹配值,由于可以在值字符串中出现转义字符,所以理论上在值字符串中可以出现任何字符,使用 [\s\S]* 进行匹配。防止匹配长度过长,需要使用非贪婪匹配,得到 "([\s\S]*?)",同样地,还要另外再匹配单引号的格式。为了及时终止,还需要匹配值之后的内容。

接着考虑值之后的内容,如果这个键值对之后还有键值对,那么后面跟的是 ,,否则,这个键值对是对象的最后一个键值对,后跟 }。但是,还有一种极端情况,如 "key": "\"Good.\", he said.,在这里,虽然 " 后跟 ,,但是值并没有结束,所以应该继续往后加内容。典型的值后面的内容应该是 , ",当然还要考虑单引号。

因此可得最终的正则表达式。

总结使用正则表达式的缺点如下:

  1. 不能得到对象和数组。
  2. 不能检查 Json 格式是否正确。

在我的实现中还有以下缺点:

  1. 不能匹配键对应的第二个值,如数组中的一系列对象。
  2. 没有转换转义字符。

结果:

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

总结

使用文法解析更优雅有趣一些,且功能更强大。使用正则虽然简单,但是设计正则匹配模式的过程很漫长,丝毫不比文法解析快。正则匹配还是不太合适。

望采纳。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题