使用 Linq 和 Newtonsoft 反序列化并转换为另一个 class

deserialize and convert to another class using Linq and Newtonsoft

我有这样的模型

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

这个 class 的 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 字符串并像这样构造一个 class。这样 DocumentDetailsIsApproved 就会出现在单个 class

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 来实现这一点?

试试这个

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

人 json 格式

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

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

开始之前

为了能够测试解决方案,我对您的 classes

进行了一些小改动
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 },
};

天真的方法

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 Casing,在 json 中使用了 camel Casing,所以我们需要定义它们之间的转换 nameof(Person.Age).ToLower()
    • 索引运算符returns一个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 class,那么您可能更喜欢 JsonPropertyAttribute 而不是此自定义映射。

我们还可以定义一个函数(或 local function)接收半解析的 json 和一个 属性 选择器(p => 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

        };