繁体   English   中英

"在流中验证 JSON"

[英]Validating JSON in a 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);
    }
}

其想法是读者将在 JSON 文件进入时对其进行遍历,并在处理每个令牌时将其写出。 如果输入中有错误,它会抛出一个异常,我可以通过向用户返回错误消息来处理该异常。

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

有没有办法从蒸汽中处理大型 JSON 对象,确保它们确实是有效的 JSON 并将它们流出来,而不必一次将整个对象保存在内存中)。

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

{
  "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
  ]
}

来自这里的建议: https ://www.newtonsoft.com/json/help/html/ReadingWritingJSON.htm

它是否应该读取单个令牌StartObjectPropertyName等...

至少部分回答我自己的问题,问题在这里:

outputStream.WriteToken(jsonReader);

事实证明,其中写入了令牌及其所有子代。 我认为这意味着它基本上读取整个文件。 第一个标记将是一个StartObject ,通过写出它的所有子项,它必须一直读取到EndObject标记。

使用:

outputStream.WriteToken(jsonReader, false);

不会自动读取所有子项,而是逐个逐个标记,我猜(希望)对于非常大的文件来说,内存效率更高。

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

如果您可以两次拉取流(以避免直接拉入内存)或将流保存为文件以便从中创建多个流,请使用JSchemaValidatingReader并在读取时使用空的 while 循环。 JschemaValidatingReader 将遍历整个 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
  }
}

schemaJsonValidatingReader是你对验证的架构。 您必须在扩展JsonValidator的类中执行任何自定义验证,您可以在此处了解如何执行。 验证后,您将从远程源或文件中提取流。

我有类似的要求,我需要解析 JSON 文件并提取数据的“模式”或形状。 虽然处理小型文档(在内存序列化中使用)很简单,但事实证明处理大型 JSON 文件很困难,因为您很容易遇到内存异常。

经过多次挠头并考虑了这个答案中的实现,下面的代码将流式传输 JSON 数据并构建一个示例 json 文件,当它遇到数组时,它只考虑第一项。 这样我就可以从现有的 JSON 中高效地生成 JSON“模式”。 我努力寻找其他可以从现有 JSON 生成模式的解决方案,而无需先将数据加载到内存中。

    //https://stackoverflow.com/questions/43747477/how-to-parse-huge-json-file-as-stream-in-json-net
    //https://stackoverflow.com/questions/49241890/validating-json-in-a-stream
    /// <summary>
    /// Generates sample json from raw json.
    /// When generator encounters arrays, will only consider the first item.
    /// </summary>
    /// <param name="inputStream"></param>
    /// <returns></returns>
    /// <exception cref="Newtonsoft.Json.JsonReader">Throws if input stream is invalid json.</exception>
    private async static Task<Stream> GenerateJSONSchemaAsync(Stream inputStream)
    {
        MemoryStream myOutputStream = new MemoryStream();

        using (StreamReader sr = new StreamReader(inputStream))
        using (JsonReader reader = new JsonTextReader(sr))
        using (StreamWriter textWriter = new StreamWriter(myOutputStream))
        using (JsonTextWriter outputStream = new JsonTextWriter(textWriter))
        {
            while (await reader.ReadAsync())
            {
                //If path contains [x] ignore if [x] index is > 0 (ie > than first child)
                MatchCollection match = Regex.Matches(reader.Path, @"(?<=\[).+?(?=\])");

                if (!match.Any() || !match.Any(x => int.Parse(x.Value) > 0))
                {
                    await outputStream.WriteTokenAsync(reader, false);
                }
            }
        }

        return myOutputStream;
    }

如果您希望您的 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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM