使用 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。这样 DocumentDetails
和 IsApproved
就会出现在单个 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 },
};
- 这里我使用了object(for
CPDocs
)和collection(for List<CPDocs>
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 Casing,在 json 中使用了 camel Casing,所以我们需要定义它们之间的转换
nameof(Person.Age).ToLower()
- 索引运算符returns一个
JToken
需要converted到string
(位)更高级的方法
以上代码由于重复((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
};
我有这样的模型
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。这样 DocumentDetails
和 IsApproved
就会出现在单个 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 },
};
- 这里我使用了object(for
CPDocs
)和collection(forList<CPDocs>
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
创建了一个新的 semiParsed
索引运算符需要字段名- 因为我们在 C# 对象中使用了 Pascal Casing,在 json 中使用了 camel Casing,所以我们需要定义它们之间的转换
nameof(Person.Age).ToLower()
- 索引运算符returns一个
JToken
需要converted到string
- 因为我们在 C# 对象中使用了 Pascal Casing,在 json 中使用了 camel Casing,所以我们需要定义它们之间的转换
Person
(位)更高级的方法
以上代码由于重复((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
};