Json.NET Deserialize Property Without Deserializing Parent Object Above It - c#

I am trying to take this JSON:
{
"title":"string",
"description":"string",
"date":"2021-04-19T01:05:38.000Z",
"image":"url",
"images":[
"url1",
"url2"
],
"attributes":{
"phonebrand":"x",
"phonecarrier":"y",
"forsaleby":"z",
"price":12345,
"location":"daLocation",
"type":"OFFERED"
},
"url":"url to listing"
}
And convert it into this C# Object:
public class Listing {
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("date")]
public DateTime? Date { get; set; }
[JsonProperty("image")]
public string Image { get; set; }
[JsonProperty("images")]
public string[] Images { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("price")]
public decimal Price { get; set; }
[JsonProperty("locationId")]
public int LocationId { get; set; }
[JsonProperty("categoryId")]
public int CategoryId { get; set; }
[JsonProperty("sortByName")]
public string SortByName { get; set; }
[JsonProperty("q")]
public string Q { get; set; }
[JsonProperty("location")]
public string Location { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("forsaleby")]
public string ForSaleBy { get; set; }
[JsonProperty("fulfillment")]
public string Fulfillment { get; set; }
[JsonProperty("payment")]
public string Payment { get; set; }
[JsonProperty("phonebrand")]
public string? PhoneBrand { get; set; }
[JsonProperty("phonecarrier")]
public string? PhoneCarrier { get; set; }
}
My problem is, I'm trying to deserialize properties like price and phonebrand but those properties are under an object in the JSON. So when I try to deserialize them like this, those properties can't be found and are set as null. How can I deserialize those properties without changing my C# Class to include an Attributes class? I want to do this because I think that it is a cleaner/better design compared the JSON I'm taking in.

I suggest two approaches that are very explicit and easy to follow for the next developer looking at the code.
Two classes
creating a intermediate dto class that is used for deserialisation and then creating the business logic object from that intermediate object.
var withAttributes = Deserialise<ListingDto>();
var flatObject = new Listing(withAttributes);
One class
You could provide accessors at the top level which dip into the subclasses.
public class Listing
{
public AttributesDto Attributes {get; set}
...
public string Url => Attributes.Url; // Maybe '?.Url'
}

Related

How unflattening commands to complex types

I'm not yet dependent to either Mapster or AutoMapper. For now I'm using handwritten mappings because I couldn't find a mapper who could do this with smaller code.
The problem is how do we map flatten structures to complex objects? I think a lot of people could benefit from a good mapping example for such a complex object. I've got even a mapping condition based on CopyOfficeAddressAsInvoiceAddress whether or not the office address needs to be copied as invoice address. I've looked all over the place but couldn't get it to work.
Maybe I should also use a different naming to make it more clear for the mapping algorithm?!
The biggest question could such a map being resolved by a mapper or is this to complex? Al the demo's I've seen were using dto and model objects that are quite similar to each other. I didn't get the point of mapping an object to another object that 99% similar to each other.
I have a Command (I'm using Mediatr) that looks like as follows:
public class Command : IRequest<IActionResult>
{
public string AccountName { get; set; }
public string ContactFirstName { get; set; }
public string ContactLastName { get; set; }
public string ContactEMail { get; set; }
public string ContactPhoneNumber { get; set; }
public string BankAccount { get; set; }
public string Bank { get; set; }
public string OfficeName { get; set; }
public string OfficeAddressStreet { get; set; }
public int OfficeAddressStreetNumber { get; set; }
public string? OfficeAddressStreetNumberAddition { get; set; }
public string OfficeAddressPostalcode { get; set; }
public string OfficeAddressCity { get; set; }
public string OfficeAddressCountry { get; set; }
public string? OfficeInvoiceAddressStreet { get; set; } = null;
public int? OfficeInvoiceAddressStreetNumber { get; set; } = null;
public string? OfficeInvoiceAddressStreetNumberAddition { get; set; } = null;
public string? OfficeInvoiceAddressPostalcode { get; set; } = null;
public string? OfficeInvoiceAddressCity { get; set; } = null;
public string? OfficeInvoiceAddressCountry { get; set; } = null;
//[Ignore]
public bool? CopyOfficeAddressAsInvoiceAddress { get; set; } = false;
public string? AssociationIdentifier { get; set; } = null;
}
And I want it to be mapped to the following models:
public class Account
{
public int Id { get; set; }
public string AccountName { get; set; }
public IList<Contact> Users { get; set; }
public IList<Office> Offices { get; set; }
public string Bank { get; set; }
public string BankAccount { get; set; }
public string? AssociationIdentifier { get; set; }
}
public class Office
{
public int Id { get; set; }
public string Name { get; set; }
public Address ContactAddress { get; set; }
public Address InvoiceAddress { get; set; }
public bool HeadQuarter { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Street { get; set; }
public string Postalcode { get; set; }
public int StreetNumber { get; set; }
public string StreetNumberAddition { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EMail { get; set; }
public string PhoneNumber { get; set; }
}
First of all, my experience is mainly using Automapper, and it is definitely possible to map complex types like this.
But your command does not need to be completely flat. There is nothing inherently wrong with DTOs being similar to your domain models. Using Automapper this is fairly easy as properties with the same name are mapped 1:1.
It could be that you are submitting a form with all the properties flattened in one object. In that case you could define either a seperate map for this object and each domain object.
CreateMap<AccountDto, Account>(); // mapping logic omitted
CreateMap<AccountDto, Office>();
...
Or you could map the one object to a range of objects using Tuples.
CreateMap<AccountDto, (Account, Office, ...)>(); // mapping logic omitted
But if you define seperate DTOs and make mapping profiles for them, it will probably ease your whole mapping experience. For copying the address, you can simply do something like this, in that case.
if (copyAddress)
{
office.InvoiceAddress = _mapper.Map<Address>(addressDto);
}

Parse JSON Object / Array of Strings into new Custom Class

I'm trying to parse some JSON data and ultimatley store it in a database.
I'm having issues when storing a collection of strings / values which are not objects themselves.
For example - The "callingCodes" & "altSpellings"
I want to store these in an SQL table which will have reference to the country they belong to.
This is an example of the JSON:
{
"name":"Puerto Rico",
"topLevelDomain":[
".pr"
],
"alpha2Code":"PR",
"alpha3Code":"PRI",
"callingCodes":[
"1787",
"1939"
],
"capital":"San Juan",
"altSpellings":[
"PR",
"Commonwealth of Puerto Rico",
"Estado Libre Asociado de Puerto Rico"
],
"region":"Americas",
"subregion":"Caribbean",
"population":3474182,
"latlng":[
18.25,
-66.5
]
},
I originally created some C# classes to represent the data using http://json2csharp.com/
This sugguested I store the values as a list of strings, which I did:
public List<string> CallingCodes { get; set; }
I now want to store the data in a table, so I created a class "TopLevelDomain" to store / link the data to the parent country:
public class CallingCode
{
public int ID { get; set; }
public int CountryID { get; set; }
public string Code{ get; set; }
}
So I altered the parent to be as follows:
public ICollection<CallingCode> CallingCodes { get; set; }
Is it possible to direct the string values into the "Code" property of my new class?
Or am I trying to crowbar two pieces of logic into one?
Is the correct way to have models for the JSON, and manually restructure these into my new DB / Entity Framework Models?
This is the auto-generated class you get from such JSON. The tricky bit here is List of primitive types.
public class RootObject
{
public string name { get; set; }
public List<string> topLevelDomain { get; set; }
public string alpha2Code { get; set; }
public string alpha3Code { get; set; }
public List<string> callingCodes { get; set; }
public string capital { get; set; }
public List<string> altSpellings { get; set; }
public string region { get; set; }
public string subregion { get; set; }
public int population { get; set; }
public List<double> latlng { get; set; }
}
Certain databases like PostgreSQL supports array as primitive type. If you are using PostgreSQL then you can perhaps make those properties array of primitive type and store them on server as is.
For other databases which does not support array, you cannot store a list of primitive values into single column of database. The easiest way to deal with it is to introduce serialization and create single string which can be stored to server. So looking at above class, for public List<string> topLevelDomain property, you can rewrite it in following way,
[NotMapped]
public List<string> topLevelDomain
{
get => Deserialize(TopLevelDomainString);
set => TopLevelDomainString = Serialize(value);
}
public string TopLevelDomainString { get; set; }
With NotMapped attribute EF will not map topLevelDomain property. But TopLevelDomainString will be persisted to database and it will get values from topLevelDomain. As for Serialize/Deserialize methods, you can use any serialization method. You can use JsonSerializer directly (since you are already using JSON objects. Or you can just combine strings using , as delimiter and split string from server using it.
Starting with EF Core 2.1 version, you can use Value-Conversion feature directly to provide funcs to do conversion (essentially serialization code like above) to EF and EF will do it while reading/saving data from/to server. This will avoid you having to create additional CLR property.
Here is your auto-generated class:
public class RootObject
{
public string name { get; set; }
public List<string> topLevelDomain { get; set; }
public string alpha2Code { get; set; }
public string alpha3Code { get; set; }
public List<string> callingCodes { get; set; }
public string capital { get; set; }
public List<string> altSpellings { get; set; }
public string region { get; set; }
public string subregion { get; set; }
public int population { get; set; }
public List<double> latlng { get; set; }
}
Let's prepare another simple one:
public class MyRootObject
{
public MyRootObject(RootObject root)
{
Name = root.name;
List<CallingCode> callingCodesConverted = new List<CallingCode>();
foreach (string code in root.callingCodes)
{
CallingCode newCode = new CallingCode() { Code = code };
callingCodesConverted.Add(newCode);
}
CallingCodes = callingCodesConverted;
}
public string Name { get; set; }
public List<CallingCode> CallingCodes { get; set; }
}
Now you could first do encoding from json to class RootObject, and then create MyRootObject based on it:
string path = #"D:\test.txt";
var r = new StreamReader(path);
var myJson = r.ReadToEnd();
RootObject root = Json.Decode<RootObject>(myJson);
MyRootObject myroot = new MyRootObject(root);
Sure MyRootObject is only an example.
Is the correct way to have models for the JSON, and manually
restructure these into my new DB / Entity Framework Models?
Well some might use that in their code and some might do even worse than that. But, I personally like to use models/dtos for the things I could and know about the data.
am I trying to crowbar two pieces of logic into one?
Yes. But, it depends. Either strongly type/define objects and all or just Ser/Deser everytime.
Your data is known (no unkown properties or anything, so why serialize and deserialize it everytime?)
Solution 1 : if you use the JSON as is to create a DB Entry
Entites
public class Country //assuming this is country data
{
public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("alpha2Code")]
public string Alpha2Code { get; set; }
[JsonProperty("alpha3Code")]
public string Alpha3Code { get; set; }
[JsonProperty("capital")]
public string Capital { get; set; }
[JsonProperty("region")]
public string Region { get; set; }
[JsonProperty("subregion")]
public string Subregion { get; set; }
[JsonProperty("population")]
public long Population { get; set; }
[JsonProperty("topLevelDomain")]
public virtual List<TopLevelDomain> TopLevelDomains { get; set; }
[JsonProperty("callingCodes")]
public virtual List<CallingCodes> CallingCodes { get; set; }
[JsonProperty("altSpellings")]
public virtual List<AltSpellings> AltSpellings { get; set; }
[JsonProperty("latlng")]
public virtual List<Coordinates> Coordinates { get; set; }
}
public class TopLevelDomain
{
public int Id { get; set; }
[ForeignKey("Country")]
public int CountryId {get; set; }
public virtual Country Country { get; set; }
public string DomainName { get; set; }
}
public class CallingCodes
{
public int Id { get; set; }
[ForeignKey("Country")]
public int CountryId {get; set; }
public virtual Country Country { get; set; }
public string Code { get; set;} // either store it as String
//OR
public long Code { get; set;}
}
public class AltSpellings
{
public int Id { get; set; }
[ForeignKey("Country")]
public int CountryId {get; set; }
public virtual Country Country { get; set; }
public string AltSpelling { get; set; }
}
public class Coordinates
{
public int Id { get; set; }
[ForeignKey("Country")]
public int CountryId {get; set; }
public virtual Country Country { get; set; }
public double Coordinates { get; set; } //again either as string or double, your wish. I would use double
}
Use it like so
//assuming using Newtonsoft
var myJson = ....assuming one Country;
var myJsonList = ...assuming List<Country>;
var country = JsonConvert.DeserializeObject<Country>(myJson);
var countries = JsonConvert.DeserializeObject<List<Country>>(myJson);
Solution 2 : First one can cause too many tables for a little data but First solution is a little more object based and typed, so Here is another one
Entity
public class Country //assuming this is country data
{
public int Id { get; set; }
public string Name { get; set; }
public string Alpha2Code { get; set; }
public string Alpha3Code { get; set; }
public string Capital { get; set; }
public string Region { get; set; }
public string Subregion { get; set; }
public long Population { get; set; }
public string TopLevelDomains { get; set; }
public string CallingCodes { get; set;}
public string AltSpellings { get; set; }
public double Longitude { get; set;}
public double Latitude { get; set; }
}
Model
public class CountryJson
{
public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("alpha2Code")]
public string Alpha2Code { get; set; }
[JsonProperty("alpha3Code")]
public string Alpha3Code { get; set; }
[JsonProperty("capital")]
public string Capital { get; set; }
[JsonProperty("region")]
public string Region { get; set; }
[JsonProperty("subregion")]
public string Subregion { get; set; }
[JsonProperty("population")]
public long Population { get; set; }
[JsonProperty("topLevelDomain")]
public List<string> TopLevelDomains { get; set; }
[JsonProperty("callingCodes")]
public List<string> CallingCodes { get; set;}
[JsonProperty("altSpellings")]
public List<string> AltSpellings { get; set; }
[JsonProperty("latlng")]
public List<string> Latlng { get; set; }
}
Use it like so
//assuming using Newtonsoft
var myJson = ....assuming one Country;
var countryJson = JsonConvert.DeserializeObject<CountryJson>(myJson);
//Write a Mapper Or Manual Map like below
var countryEntity = new Country
{
Name = countryJson.Name,
...
TopLevelDomains = JsonConvert.Serialize(countryJson.TopLevelDomains),
CallingCodes = JsonConvert.Serialize(countryJson.CallingCodes),
...//same for all list (NOTE: YOU NEED TO DESERIALIZE IT WHEN YOU FETCH IT FROM DB
Longitude = countryJson.Latlng.ElementAt(0),//assuming 0 is longi, 1 is lat
Latitude = countryJson.Latlng.ElementAt(1)//you can do it like above as well as string if you want
}

Unable to deserialize JSON in c#

I am getting the below JSON in response from a REST API.
{
"data":{
"id":123,
"zoneid":"mydomain.com",
"parent_id":null,
"name":"jaz",
"content":"172.1 6.15.235",
"ttl":60,
"priority":null,
"type":"A",
"regions":[
"global"
],
"system_record":false,
"created_at":"2017-09-28T12:12:17Z",
"updated_at":"2017-09-28T12:12:17Z"
}
}
and trying to resolve using below code but that doesn't result in a correctly deserialized type.
var model = JsonConvert.DeserializeObject<ResponseModel>(response);
below is a class according the field I received in JSON response.
public class ResponseModel
{
public int id { get; set; }
public string zone_id { get; set; }
public int parent_id { get; set; }
public string name { get; set; }
public string content { get; set; }
public int ttl { get; set; }
public int priority { get; set; }
public string type { get; set; }
public string[] regions { get; set; }
public bool system_record { get; set; }
public DateTime created_at { get; set; }
public DateTime updated_at { get; set; }
}
What is missing?
You're missing a wrapper class.
public class Wrapper
{
public ResponseModel data {get;set}
}
and then do:
var model = JsonConvert.DeserializeObject<Wrapper>(response).data;
to get the instance of your ResponseModel out the data property.
You can deduct this from your json:
{ "data":
{ "id":123, /*rest omitted */ }
}
The type that will receive this JSON needs to have a property named data. The suggested Wrapper class acts as that type.
According to json2csharp website, your model seems to be incorrect. Try this one :
public class ResponseModel
{
public int id { get; set; }
public string zoneid { get; set; }
public object parent_id { get; set; }
public string name { get; set; }
public string content { get; set; }
public int ttl { get; set; }
public object priority { get; set; }
public string type { get; set; }
public List<string> regions { get; set; }
public bool system_record { get; set; }
public DateTime created_at { get; set; }
public DateTime updated_at { get; set; }
}
public class RootObject
{
public ResponseModel data { get; set; }
}
Here a cool trick you can do in Visual Studio 2015-2017 where it generates the the correct class if you just copy the JSON (ctrl + c).
You need to create a new class in visual studio and once inside the class go to Edit menu -> Paste special -> paste JSON As Classes.
Steps to generate json class
This will generate the C# object for that json for you and save you all the hassle :)
Your model does not match your response - it matches the data property. Simply wrap another object round it
public class ResponseData
{
public ResponseModel Data {get; set; {
}
and then
var model = JsonConvert.DeserializeObject<ResponseData>(response);

How to deserialize json into collection of Objects

I have the following JSON (I can't change the incoming JSON as it is from a 3rd party system):
{
"id": 23,
"userName":"test#test.com",
"tags":
{
"Employee ID":
{
"name":"Employee ID",
"value":"123456789"
},
"Job Family":
{
"name": "Job Family",
"value": "Accounting and Finance"
}
}
}
First, I tried to deserialize using this class structure:
public class User
{
[JsonProperty("id")]
public int ID { get; set; }
[JsonProperty("username")]
public string Email { get; set; }
[JsonProperty("tags")]
public TagsJson Tags { get; set; }
}
public class TagsJson
{
[JsonProperty("tags")]
public List<Tag> Tags { get; set; }
}
public class Tag
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("value")]
public string Value { get; set; }
}
In using Newtonsoft's JsonConvert.DeserializeObject(json);, the User.Tags property is always empty. This is obviously because there are no "Tags" under the "Tags" attribute.
Yet, if I change the User class as follows...
public class User
{
[JsonProperty("id")]
public int ID { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("tags")]
public List<Tag> Tags { get; set; }
// I even tried Tag[] instead of List<Tag> here
}
...I get the following error:
Additional information: Cannot deserialize the current JSON object
(e.g. {"name":"value"}) into type
'System.Collections.Generic.List`1[Tag]' because the type requires
a JSON array (e.g. [1,2,3]) to deserialize correctly.
Any suggestions on creating the User class to allow it to deserialize correctly would be greatly appreciated.
EDIT
So this may or may not be the best answer, but it works. The tags coming in will be one of a dozen different names. So I created a Tags class with a property for each of the 12 possibilites. Since none are required, any of them that do appear will get populated. Here's my adjusted User, Tags (formerly TagsJson) and Tag classes below - but I'm definitely interested in a better solution.
public class User
{
[JsonProperty("id")]
public int ID { get; set; }
[JsonProperty("username")]
public string Email { get; set; }
[JsonProperty("tags")]
public Tags AllTags { get; set; }
}
public class Tags
{
[JsonProperty("Employee ID")]
public Tag EmployeeID { get; set; }
[JsonProperty("Job Family")]
public Tag JobFamily { get; set; }
// ... and 10 more properties for additional tags that may appear but are not required
}
public class Tag
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("value")]
public string Value { get; set; }
}
The suggested answer of this question does not work in my case because the JSON is not an array or a collection but a list of unique attribute names with a name/value pairing.
Well using the Visual Studio paste Json as classes yields...
public class Rootobject
{
public int id { get; set; }
public string userName { get; set; }
public Tags tags { get; set; }
}
public class Tags
{
[JsonProperty(PropertyName = "Employee ID")]
public EmployeeID EmployeeID { get; set; }
[JsonProperty(PropertyName = "Job Family")]
public JobFamily JobFamily { get; set; }
}
public class EmployeeID
{
public string name { get; set; }
public string value { get; set; }
}
public class JobFamily
{
public string name { get; set; }
public string value { get; set; }
}
I had to add in the JsonProperty attributes myself. However I think a better solution would be...
public class Rootobject2
{
public int id { get; set; }
public string userName { get; set; }
public IDictionary<string, NameValuePair> tags { get; set; }
}
public class NameValuePair
{
public string name { get; set; }
public string value { get; set; }
}
Although the paste Json as classes was a good starting point to easily copy and paste the code into the favoured solution.
Some test code...
string json = Resource1.String1;
Rootobject test = JsonConvert.DeserializeObject<Rootobject>(json);
Rootobject2 test2 = JsonConvert.DeserializeObject<Rootobject2>(json);
Not sure your JSON is correct but why is there space in your property name Employee ID or Job Family. Fix those in Tags class and we are good to go.
public class EmployeeID
{
public string name { get; set; }
public string value { get; set; }
}
public class JobFamily
{
public string name { get; set; }
public string value { get; set; }
}
public class Tags
{
public EmployeeID __invalid_name__Employee ID { get; set; }
public JobFamily __invalid_name__Job Family { get; set; }
}
public class User
{
public int id { get; set; }
public string userName { get; set; }
public Tags tags { get; set; }
}
This is what i got from json2csharp.com
Your JSON shows that your tags object is an Object - not an Array. You cannot deserialize an Object into a C# List because they are different structures.
If the keys in tags are dynamic/changing, then perhaps try
[JsonProperty("tags")]
Dictionary<string, string> Tags { get; set; }
Edit
It appears that your JSON is not well-formed; if you aren't able to modify it, you might have to use a custom JsonConverter.

Serializing object to dictionary with json,net

Can json.net serialize class objects into a simple dictionary?
public class CommitData
{
public AddressDTO Property { get; set; }
public DateTime? Appointment { get; set; }
public DateTime? Closed { get; set; }
public DateTime? Created { get; set; }
public string LenderRef { get; set; }
public string YearBuilt { get; set; }
public IDictionary<string, object> Custom { get; set; }
}
public class AddressDTO
{
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Address3 { get; set; }
public string Town { get; set; }
public string County { get; set; }
public string Postcode { get; set; }
}
When serializing I want to get:
{
"Property.Address1": "",
"Property.Address2": "uyg",
"LenderRef": "yguyg",
"Closed": date_here
}
And similarly deserialize a json string like above into the CommitData object?
Yes, but it is a bit of work. You have full control of how your object is serialized and deserialized by implementing your own JsonConverter. With that, you can flatten it and unflatten it by doing simple string manipulation. A proper generic solution could be acomplished but would take a lot more work as you would need to consider multiple levels of recursion on each property but if you only care about this case in particular then use Custom JsonConverter
Here is an example of a converter Json.net uses internally:
KeyValuePairConverter.cs
Hope it helps.

Categories