Custom deserialization with System.Text.Json - Grouping fields into object - c#

Using System.Text.Json and .NET Core 3.1, how can I deserialize the following JSON for a membership:
{
"id": 123,
"firstName": "James",
"lastName": "Smith",
"group": "Premium"
"state": "Active"
}
Classes:
public class Membership
{
public Member Member { get; set; }
public string Group { get; set; }
public string State { get; set; }
}
and
public class Member
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Thanks!

Personally, I would create a POCO / Model to match the Json exactly as it is brought in and deserialize to that. Then add a constructor to your Membership class that accepts the incoming, deserialized json model and builds up your object as desired.
It's an extra step in between getting the json and returning your own model, but since the source (incoming json) doesn't structurally match the destination (your poco object), translation has to happen somewhere. I find it easiest to follow the translation when there is this explicit separation in my code.
Something like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
public class Program
{
// I wrapped in [] to make a list
const string inJson = #"[{
""id"": 123,
""firstName"": ""James"",
""lastName"": ""Smith"",
""group"": ""Premium"",
""state"": ""Active""
}]";
public static void Main()
{
var deserialized = JsonSerializer.Deserialize<List<JsonMember>>(inJson);
var asMembership = deserialized.Select(i => new Membership(i)).ToList();
foreach(var m in asMembership){
Console.WriteLine($"Group: {m.Group}");
Console.WriteLine($"State: {m.State}");
Console.WriteLine($"Member Id: {m.Member.Id}");
Console.WriteLine($"Member First Name: {m.Member.FirstName}");
Console.WriteLine($"Member Last Name: {m.Member.LastName}");
}
}
}
public class JsonMember{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("firstName")]
public string FirstName { get; set; }
[JsonPropertyName("lastName")]
public string LastName { get; set; }
[JsonPropertyName("group")]
public string Group { get; set; }
[JsonPropertyName("state")]
public string State { get; set; }
}
public class Membership
{
public Member Member { get; set; }
public string Group { get; set; }
public string State { get; set; }
public Membership(JsonMember jsonMember){
Group = jsonMember.Group;
State = jsonMember.State;
Member = new Member{
Id = jsonMember.Id,
FirstName = jsonMember.FirstName,
LastName = jsonMember.LastName
};
}
}
public class Member
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
output:
Group: Premium
Group: Active
Member Id: 123
Member First Name: James
Member Last Name: Smith
See:
https://dotnetfiddle.net/y0i6Sx

fix your json by adding coma after "Premium"
"group": "Premium",
and try this
var json=...your json
var jD= JsonSerializer.Deserialize<Root>(json);
var memberShip = new Membership
{
Member = new Member { FirstName = jD.firstName, LastName = jD.lastName},
Group=jD.group,
State=jD.state
};
var output= JsonSerializer.Serialize(memberShip);
output
{
"Member": {
"Id": 0,
"FirstName": "James",
"LastName": "Smith"
},
"Group": "Premium",
"State": "Active"
}
Root class
public class Root
{
public int id { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
public string group { get; set; }
public string state { get; set; }
}

Related

How to map json response to the model with different field names

I am using an ASP.NET Core 6 and System.Text.Json library.
For example, I'm getting a response from the some API with the following structure
{
"items":
[
{
"A": 1,
"User":
{
"Name": "John",
"Age": 21,
"Adress": "some str"
},
},
{
"A": 2,
"User":
{
"Name": "Alex",
"Age": 22,
"Adress": "some str2"
},
}
]
}
And I want to write this response to the model like List<SomeEntity>, where SomeEntity is
public class SomeEntity
{
public int MyA { get; set; } // map to A
public User MyUser { get; set; } // map to User
}
public class User
{
public string Name { get; set; }
public string MyAge { get; set; } // map to Age
}
How could I do it?
UPDATE:
Is it possible to map nested properties?
public class SomeEntity
{
// should I add an attribute [JsonPropertyName("User:Name")] ?
public string UserName{ get; set; } // map to User.Name
}
Use the JsonPropertyName attribute
public class Model
{
[JsonPropertyName("items")]
public SomeEntity[] Items { get; set; }
}
public class SomeEntity
{
[JsonPropertyName("A")]
public int MyA { get; set; } // map to A
[JsonPropertyName("User")]
public User MyUser { get; set; } // map to User
}
public class User
{
public string Name { get; set; }
[JsonPropertyName("Age")]
public string MyAge { get; set; } // map to Age
}
You can then deserialize it with something like
JsonSerializer.Deserialize<Model>(response);
try this please
[JsonConverter(typeof(JsonPathConverter))]
public class SomeEntity
{
[JsonProperty("items.User.Name")]
public string UserName{ get; set; } // map to User.Name
}
deserialize using :
JsonSerializer.Deserialize<SomeEntity>(response);

Deserialize JSON using specific properties

I'm trying to deserialize JSON without declaring every property in C#. Here is a cut-down extract of the JSON:
{
"resourceType": "export",
"type": "search",
"total": 50,
"timestamp": "2020-08-02T18:26:06.747+00:00",
"entry": [
{
"url": "test.com/123",
"resource": {
"resourceType": "Slot",
"id": [
"123"
],
"schedule": {
"reference": {
"value": "testvalue"
}
},
"status": "free",
"start": "2020-08-03T08:30+01:00",
"end": "2020-08-03T09:00+01:00"
}
}
]
}
I want to get the values out of entry → resource, id and start.
Any suggestions on the best way to do this?
I've made very good experiences with json2sharp. You can enter your JSON data there and it will generate the classes you need to deserialize the JSON data for you.
public class Reference
{
public string value { get; set; }
}
public class Schedule
{
public Reference reference { get; set; }
}
public class Resource
{
public string resourceType { get; set; }
public List<string> id { get; set; }
public Schedule schedule { get; set; }
public string status { get; set; }
public string start { get; set; }
public string end { get; set; }
}
public class Entry
{
public string url { get; set; }
public Resource resource { get; set; }
}
public class Root
{
public string resourceType { get; set; }
public string type { get; set; }
public int total { get; set; }
public DateTime timestamp { get; set; }
public List<Entry> entry { get; set; }
}
The next step is to choose a framework which will help you to deserialize. Something like Newtonsoft JSON.
Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
If you want to get the data without declaring classes, you can use Json.Net's LINQ-to-JSON API (JToken, JObject, etc.). You can use the SelectToken method with a JsonPath expression to get what you are looking for in a couple of lines. Note that .. is the recursive descent operator.
JObject obj = JObject.Parse(json);
List<string> ids = obj.SelectToken("..resource.id").ToObject<List<string>>();
DateTimeOffset start = obj.SelectToken("..resource.start").ToObject<DateTimeOffset>();
Working demo here: https://dotnetfiddle.net/jhBzl4
If it turns out there are actually multiple entries and you want to get the id and start values for all of them, you can use a query like this:
JObject obj = JObject.Parse(json);
var items = obj["entry"]
.Children<JObject>()
.Select(o => new
{
ids = o.SelectToken("resource.id").ToObject<List<string>>(),
start = o.SelectToken("resource.start").ToObject<DateTimeOffset>()
})
.ToList();
Demo: https://dotnetfiddle.net/Qe8NB7
I am not sure why you don't deserialize the lot (even if it's minimally populated) since you have to do the inner classes anyway.
Here is how you could bypass some of the classes (1) by digging into the JObjects
Given
public class Reference
{
public string value { get; set; }
}
public class Schedule
{
public Reference reference { get; set; }
}
public class Resource
{
public string resourceType { get; set; }
public List<string> id { get; set; }
public Schedule schedule { get; set; }
public string status { get; set; }
public string start { get; set; }
public string end { get; set; }
}
public class Entry
{
public string url { get; set; }
public Resource resource { get; set; }
}
You could call
var results = JObject.Parse(input)["entry"]
.Select(x => x.ToObject<Entry>());

How to map a nested json object to a POCO with multiple custom classes using AutoMapper?

I tried several answered questions related to my problem but I can't seem to resolve the issue.
Here is the JSON object I am sending to my API:
{
"userName": "Test_06",
"password": "#00a00B00c",
"firstName": "Test",
"lastName": "Test",
"address": {
"houseNumber": 1,
"appartementBus": "A bus 34",
"street": "Teststreet",
"ZIP": "0000",
"country": "Test"
}
}
And I am trying to map the address object into the Address POCO:
using System.Collections.Generic;
namespace Kubex.Models
{
public class Address
{
public int Id { get; set; }
public int HouseNumber { get; set; }
public string AppartementBus { get; set; }
public int StreetId { get; set; }
public virtual Street Street { get; set; }
public int ZIPId { get; set; }
public virtual ZIP ZIP { get; set; }
public int CountryId { get; set; }
public virtual Country Country { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<Company> Companies { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
}
The following types are basically the same, example of Country looks like
using System.Collections.Generic;
namespace Kubex.Models
{
public class Country
{
public byte Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
}
The error I am getting back from Postman is the following:
{
"errors": {
"address.ZIP": [
"Error converting value \"0000\" to type 'Kubex.Models.ZIP'. Path 'address.ZIP', line 10, position 18."
],
"address.street": [
"Error converting value \"Teststreet\" to type 'Kubex.Models.Street'. Path 'address.street', line 9, position 27."
],
"address.country": [
"Error converting value \"Test\" to type 'Kubex.Models.Country'. Path 'address.country', line 11, position 22."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|cca8afa7-463e762ee0ed670d."
}
I tried to create mappings for every Type, from string to Type. but I keep getting the errors.
Looking like this: Also tried ConstructUsing.
CreateMap<string, Country>()
.ConvertUsing(s => new Country { Name = s });
CreateMap<string, ZIP>()
.ConvertUsing(s => new ZIP { Code = s });
CreateMap<string, Street>()
.ConvertUsing(s => new Street { Name = s });
Do I need to create a map for Address too? If so, how should I do that because I don't know how it sees the object, and how I should create a map for it.
Many thanks in advance.
EDIT:
This is part of the DTO I am sending up the API endpoint to register a user,
This is the mapping used:
CreateMap<UserRegisterDTO, User>();
And this is how the DTO looks like:
using Kubex.Models;
namespace Kubex.DTO
{
public class UserRegisterDTO
{
public string UserName { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
}
}
This is how the User class looks like:
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;
namespace Kubex.Models
{
public class User : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmployeeNumber { get; set; }
public int? AddressId { get; set; }
public virtual Address Address { get; set; }
public virtual ICollection<License> Licenses { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
}
In my service, I use this line to do the mapping:
var newUser = _mapper.Map<User>(dto);
If I understand correctly, Address class used in UserRegisterDTO is the same as in User. Not saying that this is a directly an issue, but from architecture standpoint it is, as you're mixing your DTO with database model (I guess), DTO should reference only simple types or other DTO's.
Hence, I would rather have something like this:
public class UserRegisterDTO
{
public string UserName { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public AddressDTO Address { get; set; }
}
public class AddressDTO {
public string HouseNumber { get;set; }
public string AppartementBus { get;set; }
public string Street { get;set; }
public string ZIP { get;set; }
public string Country { get;set; }
}
and may be then mapping from AddressDTO to Address:
CreateMap<AddressDTO, Address>()
.ForMember(d => d.Country, d => d.MapFrom(s => new Country { Name = s.Country }))
// cut for brevity
But I still don't like this. As this mapping would create country instance with each request and depending on your setup it might even create new row in database. May be better for it would be to lookup country within existing ones?
You can pass list of country instances through mapping context and use in ResolveUsing, but I personally prefer not to have complex logic in AutoMapper mappings, as that scatters business logic in at least two places and complicates unit testing.
Automapper is great for doing simple things, but if you feel that it is pushed to its limits, it is better to do at least some parts of the mapping manually in my opinion.

Pass Nested Deserialized JSON from Controller to View

After using HttpClient class to convert my JSON to a string and deserialize it with
var response = Res.Content.ReadAsStringAsync().Result;
data = JsonConvert.DeserializeObject<List<Employee>>(response);
How do I pass the data that I receive in the Controller from the call using the Model below to the View?
public class RuleType
{
public int Id { get; set; }
public string Description { get; set; }
public bool Inactive { get; set; }
}
public class RuleCategory
{
public int Id { get; set; }
public string Description { get; set; }
public bool Inactive { get; set; }
}
public class Employee
{
public string Description { get; set; }
public object EndDateTime { get; set; }
public int Id { get; set; }
public bool Inactive { get; set; }
public int RuleAction { get; set; }
public DateTime StartDateTime { get; set; }
public RuleType RuleType { get; set; }
public RuleCategory RuleCategory { get; set; }
}
Here is one object from the call
[
{
"Description": "Test Description",
"EndDateTime": null,
"Id": 1,
"Inactive": false,
"RuleAction": -2,
"StartDateTime": "2017-01-06T14:58:58Z",
"RuleType": {
"Id": 6,
"Description": "Test Description",
"Inactive": false
},
"RuleCategory": {
"Id": 1,
"Description": "Description",
"Inactive": false
}
}
]
Not sure if I'm missing something, but if you have an object you want to return to the view from the controller, you simply:
return View(viewModel); // in your case viewModel = 'data'
As others have said here already, you should be deserializing the JSON into a RootObject instead of an Employee like so:
var response = Res.Content.ReadAsStringAsync().Result;
var data = JsonConvert.DeserializeObject<List<RootObject>>(response);
You can then pass the model into the view using just:
return View(data)
You should also consider renaming RootObject into something more useful (such as employee?) as RootObject is not a very useful or descriptive name.

Retrieve the information from a nested JSON value

So I'm calling the LinkedIn API to get the profile data and it retrieves a JSON.
{
"firstName": "Cristian Viorel",
"headline": ".NET Developer",
"location": {
"country": {"code": "dk"},
"name": "Northern Region, Denmark"
},
"pictureUrls": {
"_total": 1,
"values": ["https://media.licdn.com/mpr/mprx/0_PXALDpO4eCHpt5z..."]
}
}
I can use student.firstname, student.headline. How can I get the name of the location, or the value of the pictureUrl ?
Something like student.location.name or student.pictureUrls.values ?
Pretty easy with Json.Net. You first define your model:
public class Country
{
public string code { get; set; }
}
public class Location
{
public Country country { get; set; }
public string name { get; set; }
}
public class PictureUrls
{
public int _total { get; set; }
public List<string> values { get; set; }
}
public class JsonResult
{
public string firstName { get; set; }
public string headline { get; set; }
public Location location { get; set; }
public PictureUrls pictureUrls { get; set; }
}
Then you simply parse your Json data:
string json = #"{
'firstName': 'Cristian Viorel',
'headline': '.NET Developer',
'location': {
'country': {'code': 'dk'},
'name': 'Northern Region, Denmark'
},
'pictureUrls': {
'_total': 1,
'values': ['https://media.licdn.com/mpr/mprx/0_PXALDpO4eCHpt5z...']
}
}";
JsonResult result = JsonConvert.DeserializeObject<JsonResult>(json);
Console.WriteLine(result.location.name);
foreach (var pictureUrl in result.pictureUrls.values)
Console.WriteLine(pictureUrl);
For the name yes, but for picture you need a for loop or if you just want the first item student.pictureUrls.values[0] (values seems to be an array).

Categories

Resources