C# 验证流中的JSON

C# 验证流中的JSON,c#,json,validation,json.net,C#,Json,Validation,Json.net,我上传了(可能很大)json文件,需要在其他地方写出。我希望至少做一些基本的验证(例如,确保它们是有效的JSON,甚至可能应用模式),但我希望避免将整个(同样,可能很大)文件加载到内存中,然后再将其写入。我使用的是JSON.Net,我想我可以这样做: using (var sr = new StreamReader(source)) using (var jsonReader = new JsonTextReader(sr)) using (var textWriter = new Stream

我上传了(可能很大)json文件,需要在其他地方写出。我希望至少做一些基本的验证(例如,确保它们是有效的JSON,甚至可能应用模式),但我希望避免将整个(同样,可能很大)文件加载到内存中,然后再将其写入。我使用的是JSON.Net,我想我可以这样做:

using (var sr = new StreamReader(source))
using (var jsonReader = new JsonTextReader(sr))
using (var textWriter = new StreamWriter(myoutputStream))
using (var outputStream = new JsonTextWriter(textWriter))
{
    while (jsonReader.Read())
    {
        // TODO: any addition validation!
        outputStream.WriteToken(jsonReader);
    }
}
{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [125.6, 10.1]
  },
  "properties": {
    "name": "Dinagat Islands"
  }
}
其思想是,读取器将在JSON文件进入时遍历它,并在处理每个令牌时将其写出。如果输入中有错误,它将抛出一个异常,我可以通过向用户返回错误消息来处理该异常

问题是,如果我使用一个JSON文件来逐步完成这段代码,该文件由一个具有数组属性的单个对象组成,该数组属性包含了更多对象的集合(整个文件的格式约为1.3k行),我希望它能够逐步完成。相反,它似乎只是读取整个对象,然后一步将其再次吐出

有没有一种方法可以处理steam中的大型JSON对象,确保它们确实是有效的JSON,并将它们流式输出,而不必一次将整个对象保存在内存中)

虽然答案可能更一般,但我目前试图处理的数据是GeoJson数据。一个(非常简短)的示例如下所示:

using (var sr = new StreamReader(source))
using (var jsonReader = new JsonTextReader(sr))
using (var textWriter = new StreamWriter(myoutputStream))
using (var outputStream = new JsonTextWriter(textWriter))
{
    while (jsonReader.Read())
    {
        // TODO: any addition validation!
        outputStream.WriteToken(jsonReader);
    }
}
{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [125.6, 10.1]
  },
  "properties": {
    "name": "Dinagat Islands"
  }
}
一个更长的例子可能是:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "name": "Van Dorn Street",
        "marker-color": "#0000ff",
        "marker-symbol": "rail-metro",
        "line": "blue"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [
          -77.12911152370515,
          38.79930767201779
        ]
      }
    },...//lots more objects
  ]
}
这里的建议是:


它应该读取单个标记
StartObject
PropertyName
,等等。

如果您希望JSON文件具有某种标准化的结构。然后,可以创建具有相同属性的类,然后将JSON文件反序列化为正确的类

如果JSON格式有效,则会发生反序列化。例如:

[JsonObject]
public class MyClass
{
    [JsonProperty("id")]
    public string Id {get; set;}
    [JsonProperty("name")]
    public string Name { get; set; }


    public MyClass() { }
}
然后通过以下调用反序列化JSON:

var myDeserializedJSON= JsonConvert.DeserializeObject<MyClass>(jsonData);
var myDeserializedJSON=JsonConvert.DeserializeObject(jsonData);

至少要部分回答我自己的问题,问题在于:

outputStream.WriteToken(jsonReader);
事实证明,它写的是代币和它的所有子代。我想这意味着它基本上读取整个文件。第一个标记是
StartObject
,通过将它的所有子对象都写出来,它必须一直读到
EndObject
标记

使用:

outputStream.WriteToken(jsonReader, false);
不会自动读取所有的子项,而是逐个令牌地执行,我猜(希望)对于非常大的文件,这将更加节省内存


仍然不能100%确定这是否是最有效的解决方案,除了确保它是有效的JSON之外,最好至少做一点验证。

如果您可以将流拉两次(避免直接拉入内存)或将流保存为文件,以便从中创建多个流,使用
jschemavalidationreader
并在读取时使用空while循环。JSChemaValidationReader将遍历整个JSON,而不会将其放入内存

using (var stream = fileStream.Stream)
using (var streamReader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(streamReader))
using (var validatingReader = new JSchemaValidatingReader(jsonReader) { Schema = schema })
{
  validatingReader.ValidationEventHandler += (o, a) =>
  {
      // log or output the validation errors that come up here
  };

  while (validatingReader.Read())
  {
      // Do nothing here - forces reader through the stream and validates
  }
}

JsonValidatingReader
之后的
schema
是您要验证的模式。您必须在扩展
JsonValidator
的类中执行任何自定义验证,您可以看到如何执行。验证后,您可以从远程源或文件中提取流

这需要加载整个文件。我不想反序列化,我只想验证并传递它。不加载整个文件。@MattBurland:如果没有全部检查,则无法检查整个文件。@MattBurland,如果没有全部检查,您将如何验证它?与反序列化时首先验证文件的方式相同。您检查它是否以
{
后接括号中的属性名,后接
后接值、对象或数组…如果您描述的是要验证的方式,则只读取前x行,直到您可以完成验证。然后,如果验证通过,则重新启动读取,并按照执行验证之前的方式使用对象ion.你能举一个JSON文件的例子吗?如果整个文件只包含一个对象,那么在不读取整个文件的情况下,真的没有办法验证该对象。如果文件由多个对象组成,你可以定义单个对象的开始/停止元素并读取这些块。这样做也适合answer提供了反序列化到预定义类的功能。而且,1.3k行实际上并没有那么大,如果你将它反序列化到一个对象,该对象实际消耗了多少内存?你可能会从一些不需要的东西中产生问题。@Jeremy Whoops。说得好。我忘了我们最初收到两次流来处理这个问题。交易禁令dwidth,以防止将其拉入内存,因为流非常大。我们后来只是将流保存到一个使用磁盘空间而不是内存的文件中,因为我们将所有文件处理都移动到本地。