I have a model like this
private class CPDocs
{
public string DocumentDetails { get; set; }
public bool IsApproved { get; set; }
}
and an instance of List of this class holds data like this
List<CPDocs> cd = new List<CPDocs>();
CPDocs cpd = new CPDocs();
cpd.DocumentDetails = "{\"name\":\"John\", \"age\":30, \"car\":null}";
cpd.IsApproved = false;
cd.Add(cpd);
(Real data is coming from external source and I don't have control over it. so can't change how data is added to the list)
I want to deserialize the json string and construct a class like this. So that DocumentDetails and IsApproved comes inside a single class
public class person
{
public string name { get; set; }
public string age { get; set; }
public string car { get; set; }
public bool IsApproved { get; set; }
}
Is there a way to achieve this using newtonsoft json and Linq?
CodePudding user response:
Let me show you two different ways (a naive implementation and a more advanced one)
Before we get started
In order to be able to test the solution I've applied some minor changes to your 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; }
}
CPDocsbecamepublicpersonbecamePerson- All properties of
Personuses Pascal Casing
Let's have some sample data to play with
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 },
};
- Here I have used object (for
CPDocs) and collection (forList<CPDocs>initializers to make the object creation more concise - I have also used C# 9's Target-typed new expression (
new ()) to make the object creation even more concise
Naive approach
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}");
- I've introduced a temp variable during the iteration called
semiParsedto store the semi parsed json - With the
selectstatement I've created a newPersonfor each and everydoc - The
semiParsedindex operator requires the field name- Because we have used Pascal Casing in the C# object and camel Casing in the json that's why we need to define a conversion between them
nameof(Person.Age).ToLower() - The index operator returns a
JTokenwhich needs to be converted tostring
- Because we have used Pascal Casing in the C# object and camel Casing in the json that's why we need to define a conversion between them
(Bit) more advanced approach
The above code is hard to maintain because it's repetitive ((string)semiParsed[nameof(Person....).ToLower()]).
Also what if the mapping between the json fields and C# property names are not that straight forward?
Let's define a mapping separately from the select statement
Dictionary<string, string> mapping = new()
{
{ nameof(Person.Name), "name" },
{ nameof(Person.Age), "age" },
{ nameof(Person.Car), "car" },
};
Of course if you have control over the Person class then you might prefer the JsonPropertyAttribute instead this custom mapping.
We can also define a function (or a local function) which receives the semi parsed json and a property selector (p => p.Name) and does all the magic
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];
};
With this in our hand the Linq query looks like this:
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
};
CodePudding user response:
try this
person person = JsonConvert.DeserializeObject<person>(cpd.DocumentDetails);
person.IsApproved=cpd.IsApproved;
person in json format
{
"name": "John",
"age": "30",
"car": null,
"IsApproved": false
}
