使用Newtonsoft ToObject反序列化许多Json文件

时间:2017-02-14 18:23:17

标签: c# .net json json.net

这里有什么特别低效的吗?似乎这个过程比预期的要长。我正在使用JsonArray对象解析许多JSON文件。也许有经验更丰富的人可以在这种将JSON解析为对象的方法中指出错误,从而为我节省了大量时间。

此外,内存使用率逐渐上升MB 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" ]
    }
   ]
  }

3 个答案:

答案 0 :(得分:1)

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

  1. 反序列化JSON
  2. 将对象转换两次(jdto,然后uil
  3. 保存到数据库
  4. 可能值得对代码进行分析,以确切了解哪些部分花费的时间比您预期的要长。也就是说,你可以采取一些措施来改善这段代码。

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

答案 1 :(得分:1)

您是否真的对自己的代码进行了分析?请参阅Erik Lippert的performance rant在开始调查替代方案之前,使用分析器或其他分析工具根据经验确定瓶颈所在。例如,您实际的性能问题可能在{{{ 1}} class。

话虽这么说,我的直接反应是你有太多的数据中间表示,构建,人口和垃圾收集都需要时间。其中包括:

  • BabysFirstsUsersDataEntities db可能大到large object heap,因此会永久性地损害您的流程的性能和内存使用。
  • 整个JSON层次结构的jsonString表示。
  • 每个人JToken tok

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

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

BabysFirstsUsersDataEntities

您的DTO模型看起来像http://json2csharp.com/提供的此模型:

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;
}

然后创建以下通用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<TIn, TOut>

您现在可以直接填写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<UserImageList, JsonUserImageDTO>将填充JSON的内容,并减少中间表示。

答案 2 :(得分:0)

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

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; }
}