C# json使用之Json.NET(5)——特别篇

以下内容介绍了json.NET的性能优化问题、使用JSON模式验证JSON、手动读写JSON,Json.NET提供了JsonReader和JsonWriter类、json和xml之间的转换。

性能提示

开箱即用的Json.NET比DataContractJsonSerializer和JavaScriptSerializer更快。 这里有一些提示,使它更快。

优化内存使用

为了使应用程序保持一致,最重要的是最小化.NET框架执行垃圾收集所花费的时间。 在进行垃圾收集时,分配太多对象或分配非常大的对象可能会减慢甚至停止应用程序。

为了最小化内存使用和分配的对象数量,Json.NET支持直接对流进行序列化和反序列化。 一次读取或写入JSON,而不是将整个JSON字符串加载到内存中,在处理大小超过85kb的JSON文档时尤其重要,以避免JSON字符串在大对象堆中结束。

1
2
3
4
5
6
7
HttpClient client = new HttpClient();
 
// read the json into a string
// string could potentially be very large and cause memory problems
string json = client.GetStringAsync("http://www.test.com/large.json").Result;
 
Person p = JsonConvert.DeserializeObject<Person>(json);
1
2
3
4
5
6
7
8
9
10
11
12
HttpClient client = new HttpClient();
 
using (Stream s = client.GetStreamAsync("http://www.test.com/large.json").Result)
using (StreamReader sr = new StreamReader(s))
using (JsonReader reader = new JsonTextReader(sr))
{
    JsonSerializer serializer = new JsonSerializer();
 
    // read the json from a stream
    // json size doesn't matter because only a small piece is read at a time from the HTTP request
    Person p = serializer.Deserialize<Person>(reader);
}

JsonConverters

将JsonConverter传递给SerializeObject或DeserializeObject提供了一种完全更改对象序列化方式的简单方法。 但是,有少量的开销; 为每个值调用CanConvert方法以检查是否应该由JsonConverter处理序列化。

有两种方法可以继续使用JsonConverters而无需任何开销。 最简单的方法是使用JsonConverterAttribute指定JsonConverter。 此属性告诉序列化程序在序列化和反序列化类型时始终使用该转换器,而不进行检查。

1
2
3
4
5
6
7
8
9
10
11
[JsonConverter(typeof(PersonConverter))]
public class Person
{
    public Person()
    {
        Likes = new List<string>();
    }
 
    public string Name { get; set; }
    public IList<string> Likes { get; private set; }
}

如果要转换的类不是您自己的类,并且您无法使用属性,则仍可以通过创建自己的IContractResolver来使用JsonConverter。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ConverterContractResolver : DefaultContractResolver
{
    public new static readonly ConverterContractResolver Instance = new ConverterContractResolver();
 
    protected override JsonContract CreateContract(Type objectType)
    {
        JsonContract contract = base.CreateContract(objectType);
 
        // this will only be called once and then cached
        if (objectType == typeof(DateTime) || objectType == typeof(DateTimeOffset))
        {
            contract.Converter = new JavaScriptDateTimeConverter();
        }
 
        return contract;
    }
}

上例中的IContractResolver将设置所有DateTimes以使用JavaScriptDateConverter。

Manually Serialize

读取和写入JSON的绝对最快的方法是直接使用JsonTextReader / JsonTextWriter手动序列化类型。 使用读取器或编写器直接跳过序列化器的任何开销,例如反射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static string ToJson(this Person p)
{
    StringWriter sw = new StringWriter();
    JsonTextWriter writer = new JsonTextWriter(sw);
 
    // {
    writer.WriteStartObject();
 
    // "name" : "Jerry"
    writer.WritePropertyName("name");
    writer.WriteValue(p.Name);
 
    // "likes": ["Comedy", "Superman"]
    writer.WritePropertyName("likes");
    writer.WriteStartArray();
    foreach (string like in p.Likes)
    {
        writer.WriteValue(like);
    }
    writer.WriteEndArray();
 
    // }
    writer.WriteEndObject();
 
    return sw.ToString();
}

如果性能很重要并且您不介意编写更多代码来获取它,那么这是您的最佳选择。 您可以在此处阅读有关使用JsonReader / JsonWriter的更多信息:基本解释和编写JSON

Benchmarks

使用JSON模式验证JSON

Json.NET通过JsonSchema和JsonValidatingReader类支持JSON Schema标准。 它位于Newtonsoft.Json.Schema命名空间下。

JSON Schema用于验证一块JSON的结构和数据类型,类似于XML Schema for XML。 您可以在json-schema.org上阅读有关JSON Schema的更多信息。

已过时。 JSON Schema验证已移至其自己的包中。 有关详细信息,请参阅http://www.newtonsoft.com/jsonschema。

使用JSON模式进行验证

检查JSON是否有效的最简单方法是将JSON加载到JObject或JArray中,然后将IsValid(JToken,JsonSchema)方法与JSON Schema一起使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
string schemaJson = @"{
  'description': 'A person',
  'type': 'object',
  'properties':
  {
    'name': {'type':'string'},
    'hobbies': {
      'type': 'array',
      'items': {'type':'string'}
    }
  }
}";
 
JsonSchema schema = JsonSchema.Parse(schemaJson);
 
JObject person = JObject.Parse(@"{
  'name': 'James',
  'hobbies': ['.NET', 'Blogging', 'Reading', 'Xbox', 'LOLCATS']
}");
 
bool valid = person.IsValid(schema);
// true

要获取验证错误消息,请使用IsValid(JToken,JsonSchema,IList <String>)或Validate(JToken,JsonSchema,ValidationEventHandler)重载。

1
2
3
4
5
6
7
8
9
10
11
12
JsonSchema schema = JsonSchema.Parse(schemaJson);
 
JObject person = JObject.Parse(@"{
  'name': null,
  'hobbies': ['Invalid content', 0.123456789]
}");
 
IList<string> messages;
bool valid = person.IsValid(schema, out messages);
// false
// Invalid type. Expected String but got Null. Line 2, position 21.
// Invalid type. Expected String but got Float. Line 3, position 51.

内部IsValid使用JsonValidatingReader来执行JSON Schema验证。 为了省略将JSON加载到JObject / JArray,验证JSON,然后将JSON反序列化为类的开销,JsonValidatingReader可以与JsonSerializer一起使用,以在反序列化对象时验证JSON。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
string json = @"{
  'name': 'James',
  'hobbies': ['.NET', 'Blogging', 'Reading', 'Xbox', 'LOLCATS']
}";
 
JsonTextReader reader = new JsonTextReader(new StringReader(json));
 
JsonValidatingReader validatingReader = new JsonValidatingReader(reader);
validatingReader.Schema = JsonSchema.Parse(schemaJson);
 
IList<string> messages = new List<string>();
validatingReader.ValidationEventHandler += (o, a) => messages.Add(a.Message);
 
JsonSerializer serializer = new JsonSerializer();
Person p = serializer.Deserialize<Person>(validatingReader);

创建JSON模式

获取JsonSchema对象的最简单方法是从字符串或文件加载它。

1
2
3
4
5
6
7
8
9
10
// load from a string
JsonSchema schema1 = JsonSchema.Parse(@"{'type':'object'}");
 
// load from a file
using (TextReader reader = File.OpenText(@"c:\schema\Person.json"))
{
    JsonSchema schema2 = JsonSchema.Read(new JsonTextReader(reader));
 
    // do stuff
}

也可以在代码中创建JsonSchema对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
JsonSchema schema = new JsonSchema();
schema.Type = JsonSchemaType.Object;
schema.Properties = new Dictionary<string, JsonSchema>
{
    { "name", new JsonSchema { Type = JsonSchemaType.String } },
    {
        "hobbies", new JsonSchema
        {
            Type = JsonSchemaType.Array,
            Items = new List<JsonSchema> { new JsonSchema { Type = JsonSchemaType.String } }
        }
    },
};
 
JObject person = JObject.Parse(@"{
  'name': 'James',
  'hobbies': ['.NET', 'Blogging', 'Reading', 'Xbox', 'LOLCATS']
}");
 
bool valid = person.IsValid(schema);
// true

Basic Reading and Writing JSON

为了手动读写JSON,Json.NET提供了JsonReader和JsonWriter类。

JsonTextReader and JsonTextWriter

JsonReader和JsonWriter是低级类,主要供Json.NET内部使用。 要快速使用JSON,建议使用序列化程序 - 序列化和反序列化JSON - 或使用LINQ to JSON。

JsonTextReader和JsonTextWriter用于读写JSON文本。 JsonTextWriter上有许多设置来控制JSON在写入时的格式。 这些选项包括格式,缩进字符,缩进计数和引号字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
 
using (JsonWriter writer = new JsonTextWriter(sw))
{
    writer.Formatting = Formatting.Indented;
 
    writer.WriteStartObject();
    writer.WritePropertyName("CPU");
    writer.WriteValue("Intel");
    writer.WritePropertyName("PSU");
    writer.WriteValue("500W");
    writer.WritePropertyName("Drives");
    writer.WriteStartArray();
    writer.WriteValue("DVD read/writer");
    writer.WriteComment("(broken)");
    writer.WriteValue("500 gigabyte hard drive");
    writer.WriteValue("200 gigabype hard drive");
    writer.WriteEnd();
    writer.WriteEndObject();
}
 
// {
//   "CPU": "Intel",
//   "PSU": "500W",
//   "Drives": [
//     "DVD read/writer"
//     /*(broken)*/,
//     "500 gigabyte hard drive",
//     "200 gigabype hard drive"
//   ]
// }

JsonTextReader上有设置,用于在读取文本值时读取不同的日期格式,时区和文化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
string json = @"{
   'CPU': 'Intel',
   'PSU': '500W',
   'Drives': [
     'DVD read/writer'
     /*(broken)*/,
     '500 gigabyte hard drive',
     '200 gigabype hard drive'
   ]
}";
 
JsonTextReader reader = new JsonTextReader(new StringReader(json));
while (reader.Read())
{
    if (reader.Value != null)
    {
        Console.WriteLine("Token: {0}, Value: {1}", reader.TokenType, reader.Value);
    }
    else
    {
        Console.WriteLine("Token: {0}", reader.TokenType);
    }
}
 
// Token: StartObject
// Token: PropertyName, Value: CPU
// Token: String, Value: Intel
// Token: PropertyName, Value: PSU
// Token: String, Value: 500W
// Token: PropertyName, Value: Drives
// Token: StartArray
// Token: String, Value: DVD read/writer
// Token: Comment, Value: (broken)
// Token: String, Value: 500 gigabyte hard drive
// Token: String, Value: 200 gigabype hard drive
// Token: EndArray
// Token: EndObject

JTokenReader and JTokenWriter

JTokenReader和JTokenWriter读写LINQ to JSON对象。 它们位于Newtonsoft.Json.Linq命名空间中。 这些对象允许您将LINQ to JSON对象与读取和写入JSON的对象一起使用,例如JsonSerializer。 例如,您可以从LINQ to JSON对象反序列化为常规.NET对象,反之亦然。

1
2
3
4
5
6
7
8
9
10
JObject o = new JObject(
    new JProperty("Name", "John Smith"),
    new JProperty("BirthDate", new DateTime(1983, 3, 20))
    );
 
JsonSerializer serializer = new JsonSerializer();
Person p = (Person)serializer.Deserialize(new JTokenReader(o), typeof(Person));
 
Console.WriteLine(p.Name);
// John Smith

在JSON和XML之间转换

Json.NET支持使用XmlNodeConverter将JSON转换为XML,反之亦然。

在两者之间进行转换时,都会保留元素,属性,文本,注释,字符数据,处理指令,命名空间和XML声明。 唯一需要注意的是,当它们组合成一个数组时,可能会丢失同一级别的不同命名节点的顺序。

转换规则

  • 元素保持不变。

  • 属性以@为前缀,应位于对象的开头。

  • 单个子文本节点是直接针对元素的值,否则通过#text访问它们。

  • XML声明和处理指令以?为前缀。

  • 字符数据,注释,空格和重要的空白节点分别通过#cdata-p,#comment,#whitespace和#significant-whitespace进行访问。

  • 在同一级别具有相同名称的多个节点被组合在一起成为一个数组。

  • 空元素为null。

如果从JSON创建的XML与您想要的不匹配,那么您将需要手动转换它。 执行此操作的最佳方法是将JSON加载到LINQ to JSON对象(如JObject或JArray),然后使用LINQ创建XDocument。 使用LINQ和XDocument创建JObject或JArray的相反过程也可以。 您可以在此处找到有关在LINQ中使用LINQ to JSON的更多信息。

在您的应用程序中使用的Json.NET版本将更改可用的XML转换方法。 当框架支持XmlDocument时,SerializeXmlNode / DeserializeXmlNode可用; 当框架支持XDocument时,SerializeXNode / DeserializeXNode可用。

SerializeXmlNode

JsonConvert有两个辅助方法,用于在JSON和XML之间进行转换。 第一个是SerializeXmlNode()。 此方法采用XmlNode并将其序列化为JSON文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
string xml = @"<?xml version='1.0' standalone='no'?>
<root>
  <person id='1'>
    <name>Alan</name>
    <url>http://www.google.com</url>
  </person>
  <person id='2'>
    <name>Louis</name>
    <url>http://www.yahoo.com</url>
  </person>
</root>";
 
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
 
string jsonText = JsonConvert.SerializeXmlNode(doc);
//{
//  "?xml": {
//    "@version": "1.0",
//    "@standalone": "no"
//  },
//  "root": {
//    "person": [
//      {
//        "@id": "1",
//        "name": "Alan",
//        "url": "http://www.google.com"
//      },
//      {
//        "@id": "2",
//        "name": "Louis",
//        "url": "http://www.yahoo.com"
//      }
//    ]
//  }
//}

由于同一级别具有相同名称的多个节点组合在一起形成一个数组,因此转换过程可以根据节点数生成不同的JSON。 例如,如果用户的某些XML具有单个<Role>节点,则该角色将是针对JSON“Role”属性的文本,但如果用户具有多个<Role>节点,则角色值将放入 一个JSON数组。

要解决此问题,可以添加自定义XML属性以强制创建JSON数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
string xml = @"<person id='1'>
  <name>Alan</name>
  <url>http://www.google.com</url>
  <role>Admin1</role>
</person>";
 
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
 
string json = JsonConvert.SerializeXmlNode(doc);
//{
//  "person": {
//    "@id": "1",
//    "name": "Alan",
//    "url": "http://www.google.com",
//    "role": "Admin1"
//  }
//}
 
xml = @"<person xmlns:json='http://james.newtonking.com/projects/json' id='1'>
  <name>Alan</name>
  <url>http://www.google.com</url>
  <role json:Array='true'>Admin</role>
</person>";
 
doc = new XmlDocument();
doc.LoadXml(xml);
 
json = JsonConvert.SerializeXmlNode(doc);
//{
//  "person": {
//    "@id": "1",
//    "name": "Alan",
//    "url": "http://www.google.com",
//    "role": [
//      "Admin"
//    ]
//  }
//}

DeserializeXmlNode

JsonConvert上的第二个辅助方法是DeserializeXmlNode()。 此方法接受JSON文本并将其反序列化为XmlNode。

因为有效的XML必须有一个根元素,所以传递给DeserializeXmlNode的JSON应该在根JSON对象中有一个属性。 如果根JSON对象具有多个属性,则应使用也采用元素名称的重载。 具有该名称的根元素将插入到反序列化的XmlNode中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
string json = @"{
  '?xml': {
    '@version': '1.0',
    '@standalone': 'no'
  },
  'root': {
    'person': [
      {
        '@id': '1',
        'name': 'Alan',
        'url': 'http://www.google.com'
      },
      {
        '@id': '2',
        'name': 'Louis',
        'url': 'http://www.yahoo.com'
      }
    ]
  }
}";
 
XmlDocument doc = (XmlDocument)JsonConvert.DeserializeXmlNode(json);
// <?xml version="1.0" standalone="no"?>
// <root>
//   <person id="1">
//     <name>Alan</name>
//     <url>http://www.google.com</url>
//   </person>
//   <person id="2">
//     <name>Louis</name>
//     <url>http://www.yahoo.com</url>
//   </person>
// </root
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值