【问题标题】:Deserializing many Json files using Newtonsoft ToObject使用 Newtonsoft ToObject 反序列化许多 Json 文件
【发布时间】:2017-07-03 04:17:42
【问题描述】:

这里有什么非常低效的吗?这个过程似乎比它应该花费的时间更长。我正在解析许多 JSON 文件,每个文件都有一个 JsonArray 对象。也许有更多经验的人可以指出这种将 JSON 解析为对象的方法中的错误,从而为我节省了大量时间。

此外,内存使用量缓慢上升 MB 有时会导致内存不足异常..

public void Parse(){

    using (BabysFirstsUsersDataEntities db = new BabysFirstsUsersDataEntities()
    {

           foreach (var path in Directory.EnumerateFiles(@"C:\\examplepath\").OrderBy(f => f))
           {
                    string jsonString = System.IO.File.ReadAllText(path);
                    JToken tok = JObject.Parse(jsonString);
                    Debug.WriteLine("path: " + path);

                    foreach (var x in tok.First.First)
                    {

                            JsonUserImageDTO jdto = x.ToObject<JsonUserImageDTO>();
                            UserImageList uil = jdto.ToDataModel();

                            if (uil.REID != null)
                                db.UserImageLists.Add(uil);    

                    }
            }
      db.SaveChanges();              
    }
}

每个 .json 文件中的 JSON 字符串之一的示例如下所示。请注意,这些文件大约有 1000 个,每个文件都可以有数千个这样的条目:

  {
  "results": [
    {
      "ACL": {
        "asdf": {
          "read": true,
          "write": true
        },
        "role:admin": { "read": true }
      },
      "REID": "exampleID",
      "createdAt": "datetime-string",
      "email": "example",
      "objectId": "example",
      "updatedAt": "datetimestring",
      "urlCount": 1,
      "urlList": [ "exampleurl" ]
    },
    {
      "ACL": {
        "asdf": {
          "read": true,
          "write": true
        },
        "role:admin": { "read": true }
      },
      "REID": "exampleID",
      "createdAt": "datetime-string",
      "email": "example",
      "objectId": "example",
      "updatedAt": "datetimestring",
      "urlCount": 1,
      "urlList": [ "exampleurl" ]
    }
   ]
  }

【问题讨论】:

  • 1) 您应该直接从Stream 直接从load 加载,而不是像here 解释的那样加载到一个大的中间字符串然后解析该字符串。 2) 为什么path.Contains("UserImageList") 在内部循环内?如果不是这样,您就不能完全跳过该文件吗?
  • 我会检查 #1 并报告... #2 是指文件的名称,它是我实现处理与不同模型对应的不同文件类型的剩余部分。我现在将对其进行更改,因为它使事情看起来比实际复杂一些
  • 喜欢这个? StreamReader sr = new StreamReader(path); JsonReader 阅读器 = 新 JsonTextReader(sr); var tok = JObject.Load(reader);
  • 你真的分析过你的代码吗?请参阅 Erik Lippert 的 performance rant:在开始研究替代方案之前,请使用分析器或其他分析工具凭经验确定瓶颈所在。。
  • 有多少文件,你真的需要orderby吗?该 orderby 将导致系统在开始循环之前枚举文件夹中的所有文件。

标签: c# .net json json.net


【解决方案1】:

看起来可能有几个地方会导致缓慢。

  1. 反序列化 JSON
  2. 转换对象两次(jdto,然后是uil)
  3. 保存到数据库

可能值得分析代码以准确找出哪个部分花费的时间比您预期的要长。也就是说,您可以做一些事情来总体改进此代码。

  1. 从蒸汽而不是字符串反序列化。你拥有它的方式,你基本上有两次内存中的对象 - 一次作为字符串,然后一次作为tok。有关如何使用流的信息,请参阅第二个示例 in the docs。实际上,在您的情况下,您在内存中的相同信息 4 次 - 字符串 tok、jdto 和 uil。这让我想到了下一点..
  2. 尝试消除对象的一些中间表示。通常,您放置的对象越多,等待 GC 的时间就越多。
  3. 将路径名的过滤移动到您调用EnumerateFiles()的部分。如果您不打算对文件执行任何操作,那么反序列化文件是没有意义的。

【讨论】:

  • #1:像这样? StreamReader sr = new StreamReader(path); JsonReader 阅读器 = 新 JsonTextReader(sr); var tok = JObject.Load(reader);
  • 是的,但不要忘记using 语句。
  • 如果有很多文件,即使orderby也会很慢。
【解决方案2】:

您是否真正分析过您的代码?请参阅 Erik Lippert 的 performance rant:在开始研究替代方案之前,使用分析器或其他分析工具凭经验确定瓶颈所在。例如,您的实际性能问题可能在 BabysFirstsUsersDataEntities db 类的某个地方.

话虽如此,我的直接反应是你的数据有太多的中间表示,其中的构建、填充和垃圾收集都需要时间。其中包括:

  • jsonString 可能大到足以在large object heap 上运行,因此会永久影响进程的性能和内存使用。
  • 整个 JSON 层次结构的 JToken tok 表示。
  • 每个人JsonUserImageDTO。

我的建议是尽可能多地消除这些中间表示。正如the documentation 中所建议的那样,您应该直接从流中加载,而不是加载到字符串并解析该字符串。

您还可以通过直接填充数据模型来消除JToken tok。假设您的BabysFirstsUsersDataEntities 看起来像这样(我只是在这里猜测):

public class BabysFirstsUsersDataEntities
{
    public BabysFirstsUsersDataEntities() { this.UserImageLists = new List<UserImageList>(); }

    public List<UserImageList> UserImageLists { get; set; }
}

public class UserImageList
{
    public string email { get; set; }
    public List<string> urlList;
}

你的 DTO 模型看起来像 http://json2csharp.com/ 提供的这个模型:

public class RootObjectDTO
{
    public ICollection<JsonUserImageDTO> results { get; set; }
}

public class JsonUserImageDTO
{
    public ACL ACL { get; set; }
    public string REID { get; set; }
    public string createdAt { get; set; }
    public string email { get; set; }
    public string objectId { get; set; }
    public string updatedAt { get; set; }
    public int urlCount { get; set; }
    public List<string> urlList { get; set; }

    public UserImageList ToDataModel()
    {
        return new UserImageList { email = email, urlList = urlList };
    }
}

public class Asdf
{
    public bool read { get; set; }
    public bool write { get; set; }
}

public class RoleAdmin
{
    public bool read { get; set; }
}

public class ACL
{
    public Asdf asdf { get; set; }

    [JsonProperty("role:admin")]
    public RoleAdmin RoleAdmin { get; set; }
}

然后创建以下通用ConvertingCollection&lt;TIn, TOut&gt; 实用程序类:

public class ConvertingCollection<TIn, TOut> : BaseConvertingCollection<TIn, TOut, ICollection<TIn>>
{
    readonly Func<TOut, TIn> toInner;

    public ConvertingCollection(Func<ICollection<TIn>> getCollection, Func<TIn, TOut> toOuter, Func<TOut, TIn> toInner)
        : base(getCollection, toOuter)
    {
        if (toInner == null)
            throw new ArgumentNullException();
        this.toInner = toInner;
    }

    protected TIn ToInner(TOut outer) { return toInner(outer); }

    public override void Add(TOut item)
    {
        Collection.Add(ToInner(item));
    }

    public override void Clear()
    {
        Collection.Clear();
    }

    public override bool IsReadOnly { get { return Collection.IsReadOnly; } }

    public override bool Remove(TOut item)
    {
        return Collection.Remove(ToInner(item));
    }

    public override bool Contains(TOut item)
    {
        return Collection.Contains(ToInner(item));
    }
}

public abstract class BaseConvertingCollection<TIn, TOut, TCollection> : ICollection<TOut>
where TCollection : ICollection<TIn>
{
    readonly Func<TCollection> getCollection;
    readonly Func<TIn, TOut> toOuter;

    public BaseConvertingCollection(Func<TCollection> getCollection, Func<TIn, TOut> toOuter)
    {
        if (getCollection == null || toOuter == null)
            throw new ArgumentNullException();
        this.getCollection = getCollection;
        this.toOuter = toOuter;
    }

    protected TCollection Collection { get { return getCollection(); } }

    protected TOut ToOuter(TIn inner) { return toOuter(inner); }

    #region ICollection<TOut> Members

    public abstract void Add(TOut item);

    public abstract void Clear();

    public virtual bool Contains(TOut item)
    {
        var comparer = EqualityComparer<TOut>.Default;
        foreach (var member in Collection)
            if (comparer.Equals(item, ToOuter(member)))
                return true;
        return false;
    }

    public void CopyTo(TOut[] array, int arrayIndex)
    {
        foreach (var item in this)
            array[arrayIndex++] = item;
    }

    public int Count { get { return Collection.Count; } }

    public abstract bool IsReadOnly { get; }

    public abstract bool Remove(TOut item);

    #endregion

    #region IEnumerable<TOut> Members

    public IEnumerator<TOut> GetEnumerator()
    {
        foreach (var item in Collection)
            yield return ToOuter(item);
    }

    #endregion

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion
}

您现在可以按如下方式直接填充您的db:

        var rootDTO = new RootObjectDTO
        {
            results = new ConvertingCollection<UserImageList, JsonUserImageDTO>(() => db.UserImageLists, (x) => { throw new NotImplementedException(); }, (x) => x.ToDataModel())
        };

        using (var stream = File.Open(path, FileMode.Open))
        using (var reader = new StreamReader(stream))
        {
            JsonSerializer.CreateDefault().Populate(reader, rootDTO);
        }

通过填充预分配的 rootDTO 和 ConvertingCollection&lt;UserImageList, JsonUserImageDTO&gt;,您的 db.UserImageLists 将填充 JSON 的内容,中间表示较少。

【讨论】:

    【解决方案3】:

    您可以创建对象然后反序列化它们。 示例:

    JsonConvert.DeserializeObject<RootObject>(jsonString);
    
    public class Asdf
    {
        public bool read { get; set; }
        public bool write { get; set; }
    }
    
    public class RoleAdmin
    {
        public bool read { get; set; }
    }
    
    public class ACL
    {
        public Asdf asdf { get; set; }
        public RoleAdmin { get; set; }
    }
    
    public class Result
    {
        public ACL ACL { get; set; }
        public string REID { get; set; }
        public string createdAt { get; set; }
        public string email { get; set; }
        public string objectId { get; set; }
        public string updatedAt { get; set; }
        public int urlCount { get; set; }
        public List<string> urlList { get; set; }
    }
    
    public class RootObject
    {
        public List<Result> results { get; set; }
    }
    

    【讨论】:

    • 这几乎就是 JSONUserImageDTO 和 UserImage 类正在做的事情
    • @Jaked222 - 好的,用这种方式代替你的方式有什么表现?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多