【问题标题】:deserialize and convert to another class using Linq and Newtonsoft使用 Linq 和 Newtonsoft 反序列化并转换为另一个类
【发布时间】:2022-01-03 16:51:00
【问题描述】:

我有一个这样的模型

private class CPDocs
{
    public string DocumentDetails { get; set; }
    public bool IsApproved { get; set; }
}

这个类的List的一个实例保存着这样的数据

List<CPDocs> cd = new List<CPDocs>();
CPDocs cpd = new CPDocs();
cpd.DocumentDetails = "{\"name\":\"John\", \"age\":30, \"car\":null}";
cpd.IsApproved = false;
cd.Add(cpd);

(真实数据来自外部来源,我无法控制它。因此无法更改将数据添加到列表的方式)

我想反序列化json字符串并构造一个这样的类。这样DocumentDetailsIsApproved 就在一个类中

public class person
{
    public string name { get; set; }
    public string age { get; set; }
    public string car { get; set; }
    public bool IsApproved { get; set; }
}

有没有办法使用 newtonsoft json 和 Linq 来实现这一点?

【问题讨论】:

    标签: c# linq json.net


    【解决方案1】:

    让我向您展示两种不同的方式(一种简单的实现方式和一种更高级的方式)

    开始之前

    为了能够测试解决方案,我对您的类进行了一些小改动

    public class CPDocs
    {
        public string DocumentDetails { get; set; }
        public bool IsApproved { get; set; }
    }
    
    public class Person
    {
        public string Name { get; set; }
        public string Age { get; set; }
        public string Car { get; set; }
        public bool IsApproved { get; set; }
    }
    
    • CPDocs 变成了public
    • person 变成了 Person
    • Person 的所有属性都使用 Pascal 套管

    让我们来玩一些示例数据

    List<CPDocs> cd = new()
    {
        new() { DocumentDetails = "{\"name\":\"John\", \"age\":30, \"car\":null}", IsApproved = false },
        new() { DocumentDetails = "{\"name\":\"Jane\", \"age\":27, \"car\":null}", IsApproved = true },
        new() { DocumentDetails = "{\"name\":\"Doe\", \"age\":null, \"car\":null}", IsApproved = false },
    };
    
    • 这里我使用了对象(对于CPDocs)和集合(对于List&lt;CPDocs&gt; initializers,使对象创建更加简洁
    • 我还使用了 C# 9 的 Target-typed new expression (new ()) 使对象创建更加简洁

    天真的方法

    var result = from doc in cd
    let semiParsed = JObject.Parse(doc.DocumentDetails)
    select new Person
    {
        Name = (string)semiParsed[nameof(Person.Name).ToLower()],
        Age = (string)semiParsed[nameof(Person.Age).ToLower()],
        Car = (string)semiParsed[nameof(Person.Car).ToLower()],
        IsApproved = doc.IsApproved
    };
    
    foreach (var item in result)
        Console.WriteLine($"{item.Name} ({item.Age}): '{item.Car}' {item.IsApproved}");
    
    • 我在迭代过程中引入了一个名为 semiParsed 的临时变量来存储半解析的 json
    • 使用select 语句,我为每个doc 创建了一个新的Person
    • semiParsed 索引运算符需要字段名称
      • 因为我们在 C# 对象中使用了 Pascal 大小写,在 json 中使用了骆驼大小写,这就是为什么我们需要在它们之间定义一个转换nameof(Person.Age).ToLower()
      • 索引运算符返回一个JToken,它需要是convertedstring

    (位)更高级的方法

    上面的代码很难维护,因为它是重复的 ((string)semiParsed[nameof(Person....).ToLower()])。

    如果 json 字段和 C# 属性名称之间的映射不是那么直接,该怎么办?

    让我们从select 语句中单独定义一个映射

    Dictionary<string, string> mapping = new()
    {
        { nameof(Person.Name), "name" },
        { nameof(Person.Age), "age" },
        { nameof(Person.Car), "car" },
    };
    

    当然,如果您可以控制 Person 类,那么您可能更喜欢 JsonPropertyAttribute 而不是这个自定义映射。

    我们还可以定义一个函数(或 local function),它接收半解析的 json 和一个属性选择器(p =&gt; p.Name)并完成所有的魔法

    string GetValueBasedOnSelector(JObject source, Expression<Func<Person, string>> propSelector) 
    {
        var expression = (MemberExpression)propSelector.Body;
        var propName = expression.Member.Name;
        var fieldName = mapping[propName];
    
        return (string)source[fieldName];
    };
    

    有了这个,Linq 查询看起来像这样:

    result = from doc in cd
            let semiParsed = JObject.Parse(doc.DocumentDetails)
            select new Person
            {
                Name = GetValueBasedOnSelector(semiParsed, p => p.Name),
                Age = GetValueBasedOnSelector(semiParsed, p => p.Age),
                Car = GetValueBasedOnSelector(semiParsed, p => p.Car),
                IsApproved = doc.IsApproved
    
            };
    

    【讨论】:

      【解决方案2】:

      试试这个

      person person = JsonConvert.DeserializeObject<person>(cpd.DocumentDetails);
      person.IsApproved=cpd.IsApproved;
      

      json 格式的人

      {
        "name": "John",
        "age": "30",
        "car": null,
        "IsApproved": false
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-11-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-05-07
        相关资源
        最近更新 更多