Home > database >  deserialize and convert to another class using Linq and Newtonsoft
deserialize and convert to another class using Linq and Newtonsoft

Time:01-05

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; }
}
  • CPDocs became public
  • person became Person
  • All properties of Person uses 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 (for List<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 semiParsed to store the semi parsed json
  • With the select statement I've created a new Person for each and every doc
  • The semiParsed index 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 JToken which needs to be converted to string

(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
}
  •  Tags:  
  • Related