【问题标题】:Processing JSON objects one by one in JSON stream during streaming in ASP.NET WebAPI 2在 ASP.NET WebAPI 2 中流式传输期间在 JSON 流中一一处理 JSON 对象
【发布时间】:2018-09-18 19:11:23
【问题描述】:

我正在尝试流式传输大型 JSON 文件并在流式传输期间逐项反序列化。

我用于此测试https://raw.githubusercontent.com/ysharplanguage/FastJsonParser/master/JsonTest/TestData/fathers.json.txt

问题是我没有收到错误,但我的代码似乎没有逐一处理项目,甚至没有处理 JSON 对象。

我被卡住了,真的不知道如何实现逐个对象处理流对象的部分。

这是我的代码:

using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Threading.Tasks;

namespace AMServices.Controllers
{
    public class FathersData
    {
        public Father[] fathers { get; set; }
    }

    public class Someone
    {
        public string name { get; set; }
    }

    public class Father : Someone
    {
        public int id { get; set; }
        public bool married { get; set; }
        // Lists...
        public List<Son> sons { get; set; }
        // ... or arrays for collections, that's fine:
        public Daughter[] daughters { get; set; }
    }

    public class Child : Someone
    {
        public int age { get; set; }
    }

    public class Son : Child
    {
    }

    public class Daughter : Child
    {
        public string maidenName { get; set; }
    }

    public class StreamerController : ApiController
    {
        static readonly JsonSerializer _serializer = new JsonSerializer();
        static readonly HttpClient _client = new HttpClient();

        [HttpPost]
        [Route("streamer/stream")]
        public async Task<IHttpActionResult> stream()
        {
            string apiUrl = "https://raw.githubusercontent.com/ysharplanguage/FastJsonParser/master/JsonTest/TestData/fathers.json.txt";

            using (var stream = await _client.GetStreamAsync(apiUrl).ConfigureAwait(false))
            using (var reader = new StreamReader(stream))
            using (var json = new JsonTextReader(reader))
            {
                if (json == null)
                    StatusCode(HttpStatusCode.InternalServerError);

                JsonSerializer serializer = new JsonSerializer();

                Father f = serializer.Deserialize<Father>(json);
                System.Diagnostics.Debug.WriteLine(f.name);    
            }

            return StatusCode(HttpStatusCode.OK);
        }
    }
}

更新

我已将using (var json = new JsonTextReader(reader)) 修改为

while (json.Read())
{
    if (json.TokenType == JsonToken.StartObject)
    {
        JObject objX = JObject.Load(json);
        Father f = objX.ToObject<Father>();

        System.Diagnostics.Debug.WriteLine("Name -> " + f.name);
    }
}

如何逐项更改我可以流式传输的内容以及在此过程中的内容?

【问题讨论】:

  • 可以像Task stream(Action&lt;Father&gt; callback) {}这样的流函数添加回调参数,反序列化后调用。 Async Enumerable 尚不可用,因为它是 c# 8 功能。您还可以查看响应式扩展 (Rx)
  • @Kalten:感谢您的评论。你能举个小例子吗?

标签: c# json asp.net-web-api2 streaming


【解决方案1】:

由于异步 Enumerable 不可用,但您可以使用回调系统。在以下示例中,我将反序列化逻辑包装在一个将公开事件 (FatherReaded) 的类中。

async Task Main()
{
    await stream();
}

// Define other methods and classes here
public class FathersData
{
    public Father[] Fathers { get; set; }
}

public class Someone
{
    public string Name { get; set; }
}

public class Father : Someone
{
    public int Id { get; set; }
    public bool Married { get; set; }
    // Lists...
    public List<Son> Sons { get; set; }
    // ... or arrays for collections, that's fine:
    public Daughter[] Daughters { get; set; }
}

public class Child : Someone
{
    public int age { get; set; }
}

public class Son : Child
{
}

public class Daughter : Child
{
    public string maidenName { get; set; }
}

public async Task stream()
{

    var fatherReader = new FatherReader();
    fatherReader.FatherReaded += (s, f) => {
        //f.name.Dump();
        System.Diagnostics.Debug.WriteLine(f.Name);
    };

    string apiUrl = "https://raw.githubusercontent.com/ysharplanguage/FastJsonParser/master/JsonTest/TestData/fathers.json.txt";

    using (var client = new HttpClient())
    using (var stream = await client.GetStreamAsync(apiUrl).ConfigureAwait(false))
    {
        fatherReader.Read(stream);
    }
}

public class FatherReader
{
    public event System.EventHandler<Father> FatherReaded;

    public FatherReader()
    {

    }

    private void OnFatherReaded(Father father){
        FatherReaded?.Invoke(this, father);
    }

    public void Read(Stream stream)
    {
        using (var reader = new StreamReader(stream))
        using (var jsonReader = new JsonTextReader(reader))
        {
            JsonSerializer serializer = new JsonSerializer();

            jsonReader.Read(); // Skip the first StartObject token

            while (jsonReader.Read())
            {
                if (jsonReader.TokenType == JsonToken.StartObject)
                {
                    var father = serializer.Deserialize(jsonReader, typeof(Father));
                    OnFatherReaded((Father)father);
                }
            }
        }
    }
}

相同但可观察到 (Rx) 。 Dump 方法是Console.WriteLine(this.ToString) 的扩展简写

public async Task stream()
{
    var fatherReader = new FatherReader();
    var observable = fatherReader.Observable;

    // Here you can chain many operator like Linq (filtre, transforme, ...)
    observable = observable
        .Where(f => f.Name.StartsWith("J"));

    observable.Subscribe(f => f.Name.Dump(), e => e.ToString().Dump());

    string apiUrl = "https://raw.githubusercontent.com/ysharplanguage/FastJsonParser/master/JsonTest/TestData/fathers.json.txt";

    using (var client = new HttpClient())
    using (var stream = await client.GetStreamAsync(apiUrl).ConfigureAwait(false))
    {
        fatherReader.Read(stream);
    }
}

public class FatherReader
{
    private Subject<Father> _observable = new Subject<Father>();
    public IObservable<Father> Observable => _observable.AsObservable();

    public FatherReader()
    {
    }

    private void OnFatherReaded(Father father)
    {
        _observable.OnNext(father);
    }

    public void Read(Stream stream)
    {
        try
        {
            using (var reader = new StreamReader(stream))
            using (var jsonReader = new JsonTextReader(reader))
            {
                JsonSerializer serializer = new JsonSerializer();

                jsonReader.Read(); // Skip the first StartObject token

                while (jsonReader.Read())
                {
                    if (jsonReader.TokenType == JsonToken.StartObject)
                    {
                        try
                        {
                            var father = serializer.Deserialize(jsonReader, typeof(Father));
                            OnFatherReaded((Father)father);
                        }
                        catch (Exception ex)
                        {
                            _observable.OnError(ex);
                        }
                    }
                }
            }
        }
        catch (Exception ex)
        {
            _observable.OnError(ex);
        }

        _observable.OnCompleted();
    }
}

【讨论】:

    【解决方案2】:

    您可以尝试添加RootObject 并包含List&lt;Father&gt; 属性作为您的反序列化对象,因为您的URL JSON 数据"father" 键包含一个数组而不是一个对象。

    public class RootObject
    {
        public List<Father> fathers { get; set; }
    }
    

    这样使用 API。

    public class StreamerController : ApiController
    {
        static readonly JsonSerializer _serializer = new JsonSerializer();
        static readonly HttpClient _client = new HttpClient();
    
        [HttpPost]
        [Route("streamer/stream")]
        public async Task<IHttpActionResult> stream()
        {
            string apiUrl = "https://raw.githubusercontent.com/ysharplanguage/FastJsonParser/master/JsonTest/TestData/fathers.json.txt";
    
            using (var stream = await _client.GetStreamAsync(apiUrl).ConfigureAwait(false))
            using (var reader = new StreamReader(stream))
            using (var json = new JsonTextReader(reader))
            {
                if (json == null)
                    StatusCode(HttpStatusCode.InternalServerError);
    
                JsonSerializer serializer = new JsonSerializer();
    
                RootObject f = serializer.Deserialize<RootObject>(json);  
            }
    
            return StatusCode(HttpStatusCode.OK);
        }
    }
    

    【讨论】:

    • 但是我不能逐项反序列化!?我已经通过检查 JSON StartObject 稍微修改了我的代码,但它也不起作用。
    • @I'mSTORM 您想逐项反序列化吗?如果您需要编写客户解析逻辑。
    • 因为我收到了 3-5 百万个 JSON 项目,总容量为 5-7 Gig,因此我不想在处理之前先将它们全部存储在内存中。
    • 您的自定义解析逻辑到底是什么意思?是不是跟我贴的例子很像?
    猜你喜欢
    • 1970-01-01
    • 2021-06-16
    • 2019-06-23
    • 2021-02-16
    • 2015-09-13
    • 2019-11-24
    • 1970-01-01
    • 2022-08-10
    • 2013-12-01
    相关资源
    最近更新 更多