C# deserialize recursive interfaces set a property dynamically - c#

what I want to do is be able to deserialize any IBaseModel and it will recursively set all IBaseModel to either Safe or Unsafe depending on what is making the call. I am currently using Newtonsoft but would be happy to switch to System.Text.Json if it will achieve the desired results.
I have the following structure:
public enum RequestPermissionType
{
Safe = 1,
Unsafe = 2,
}
public interface IBaseModel {
public long Id { get; set; }
public RequestPermissionType PermissionType { get; set; }
}
public class User : IBaseModel {
public long Id { get; set; }
public RequestPermissionType PermissionType { get; set; }
public string Name { get; set; }
public Address HomeAddress { get; set; }
}
public class Address: IBaseModel {
public long Id { get; set; }
public RequestPermissionType PermissionType { get; set; }
public string street { get; set; }
}
I tried creating a JsonConverter but I still want deserialize everything normally I just want to set either so when I override ReadJson i think it keeps calling deserialize using the same converter:
public override IBaseEntity ReadJson(JsonReader reader, Type objectType, [AllowNull] IBaseEntity existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
{
var result = serializer.Deserialize<IBaseEntity >(reader);
result.PermissionType = _requestPermissionType;
return result;
}
I also tried using a converter that extended CustomCreationConverter, which works but I am trying to avoid using the activator:
public class PermissionTypeConverter : CustomCreationConverter<IBaseEntity>
{
private RequestPermissionType _requestPermissionType = RequestPermissionType.Unsafe;
public PermissionTypeConverter()
{
_requestPermissionType = RequestPermissionType.Unsafe;
}
public PermissionTypeConverter(RequestPermissionType requestPermissionType)
{
_requestPermissionType = requestPermissionType;
}
public override IBaseEntity Create(Type objectType)
{
var result = Activator.CreateInstance(objectType);
(result as IBaseEntity).PermissionType = _requestPermissionType;
return result as IBaseEntity;
}
}
Edit: Adding Json data example.
[
{
"id": 1,
"name": "User 1",
"homeAddress": {
"id": 5,
"street": "Fake Street 1"
}
},
{
"id": 2,
"name": "User 2",
"homeAddress": {
"id": 6,
"street": "Fake Street 2"
}
}
]

Related

Convert JSON to list with generic types

I got a JSON response like:
{
"items": [
{
"document": {
"id": "123",
"title": "title2",
"description": "Description1",
"type": "Typ1"
}
},
{
"document": {
"id": "456",
"title": "title2",
"description": "desctiption2",
"type": "Type2",
"Type2Property": "Type2Property"
}
}
]
}
As you can see above I have two values (just for example) with different properties.
In my code, I have two classes.
public class Type1
{
public string Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Type { get; set; }
}
public class Type2
{
public string Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Type { get; set; }
public string Type2Property {get; set;}
}
Question: How can I create one generic list which combines Type1 and Type2. In the future, I can have more TypeX (with different properties). So, I'd like to parse JSON into a generic list.
Update: I can filter json by the Type property from the JSON.
One way to solve this problem is to create a custom JsonConverter and override its ReadJson method.
I've introduced a couple of helper classes to be able to parse the whole sample json:
public class TopLevel
{
public MidLevel[] Items { get; set; }
}
public class MidLevel
{
public IDocument Document { get; set; }
}
[JsonConverter(typeof(DocumentTypeConverter))]
public interface IDocument
{
}
I've created an IDocument marker interface. If you wish you can use abstract class.
I've decorated the interface with a JsonConverterAttribute and specified there the custom converter.
I've changed the Type1 and Type2 classes to implement this interface:
public class Type1 : IDocument
{
...
public string Type { get; set; }
}
public class Type2 : IDocument
{
...
public string Type { get; set; }
public string Type2Property { get; set; }
}
The DocumentTypeConverter naive implementation would look like this:
(Obviously you can make more type-safe)
public class DocumentTypeConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
=> throw new NotImplementedException();
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
switch (jObject["type"].Value<string>())
{
case "Typ1":
{
var obj = new Type1();
serializer.Populate(jObject.CreateReader(), obj);
return obj;
}
case "Type2":
{
var obj = new Type2();
serializer.Populate(jObject.CreateReader(), obj);
return obj;
}
default:
throw new Exception();
}
}
public override bool CanConvert(Type objectType)
=> objectType == typeof(IDocument);
}
The CanConvert tells us that this convert can be used against IDocuments.
The ReadJson branches its logic based on the "type" field.
The actual conversion done with the Populate instead of JsonCovert.Deserialize to avoid infinite recursion.
Finally, the usage is that simple:
static void Main(string[] args)
{
var sampleJson = File.ReadAllText("sample.json");
var sample = JsonConvert.DeserializeObject<TopLevel>(sampleJson);
Console.ReadLine();
}

Trying to create a custom Json Deserializer for Microsoft.Spatial.GeographyPoint

I am using Azure Location Service to retrieve certain locations. The Json I am returned is an array of objects that are populated like this (data omitted for privacy):
{
"Id": "",
"Name": "",
"Address": "",
"PostCode": "",
"Location": {
"Latitude": 123
"Longitude": -123,
"IsEmpty": false,
"Z": null,
"M": null,
"CoordinateSystem": {
"EpsgId": 123,
"Id": "123",
"Name": "ABC"
}
}
},
I am trying to deserialize into a LookUpModel object which has a Microsoft.Spatial.Geography point for its Location:
public GeographyPoint Location { get; set; }
When I use the default newtonsoft json deserializer, I receive the following error: "Could not create an instance of type Microsoft.Spatial.GeographyPoint. Type is an interface or abstract class and cannot be instantiated. Path '[0].Location.Latitude', line 11, position 17."
Therefore I'm trying to create a custom Json Deserializer which will create a GeographyPoint (using GeographyPoint.Create(Longitude = x, Latitude = y) whenever the location node is hit. So far I have:
public class GeographyPointJsonConverter : JsonConverter
{
private readonly Type[] _types;
public GeographyPointJsonConverter(params Type[] types)
{
_types = types;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
JObject o = (JObject)t;
IList<string> propertyNames = o.Properties().Select(p => p.Name).ToList();
o.AddFirst(new JProperty("Keys", new JArray(propertyNames)));
o.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return _types.Any(t => t == typeof(GeographyPoint));
}
}
being called with: var jsonData = JsonConvert.DeserializeObject<LookUpModel[]>(json, new GeographyPointJsonConverter(typeof(LookUpModel[])));
However I cannot work out how I can successfully deserialize the Location information into a GeographyPoint? Is anyone able to point me in the right direction here please?
You class will be look like this
public partial class MyClass
{
[JsonProperty("Id")]
public string Id { get; set; }
[JsonProperty("Name")]
public string Name { get; set; }
[JsonProperty("Address")]
public string Address { get; set; }
[JsonProperty("PostCode")]
public string PostCode { get; set; }
[JsonProperty("Location")]
public Location Location { get; set; }
}
public partial class Location
{
[JsonProperty("Latitude")]
public long Latitude { get; set; }
[JsonProperty("Longitude")]
public long Longitude { get; set; }
[JsonProperty("IsEmpty")]
public bool IsEmpty { get; set; }
[JsonProperty("Z")]
public object Z { get; set; }
[JsonProperty("M")]
public object M { get; set; }
[JsonProperty("CoordinateSystem")]
public CoordinateSystem CoordinateSystem { get; set; }
}
public partial class CoordinateSystem
{
[JsonProperty("EpsgId")]
public long EpsgId { get; set; }
[JsonProperty("Id")]
[JsonConverter(typeof(ParseStringConverter))]
public long Id { get; set; }
[JsonProperty("Name")]
public string Name { get; set; }
}
In your Json, comma (,) is missing after
"Latitude": 123
Deserialize you json into object as
MyClass obj = JsonConvert.DeserializeObject<MyClass>(json);

Deserialize JSON object from reference ID in c#

I have a list of items in json
"items":[
{
"id": 0,
"name": "Thats a name",
"type": 0,
"price": 3.5,
"ingredients": [1,0,2,3],
},
{
"id": 1,
"name": "This is AnotherName",
"type": 0,
"price": 3.7,
"ingredients": [5,0,6,10,2,8],
}
]
The type and ingredients properties are detailed in another object of the same JSON file. If I look it up, I know what a type 0 is, and what the ingredients are.
What I'm trying to achieve, in c#, is to have my data model not having int everywhere, but having the actual objects. For example, with the ingredients, my Item object has an Ingredients property of type List<Ingredient> and not List<int>.
Like the following :
public IEnumerable<Ingredient> Ingredients { get; set; }
public IEnumerable<FoodType> Types { get; set; }
public IEnumerable<FoodItem> Items { get; set; }
public class FoodItem
{
public int Id { get; set; }
public string Name { get; set; }
public int Type { get; set; }
public float Price { get; set; }
public IEnumerable<Ingredient> Ingredients { get; set; }
}
But in the current state of my deserialization, it crashes because it's looking for an int.
I've found keywords but not real help, about "PreserveReferenceHandling" or "isReference" but I'm not sure what those are and even less how to use them.
This is how I deserialize :
var json = r.ReadToEnd();
var items = JsonConvert.DeserializeObject<EatupDataModel>(json);
I know the following would work :
Change the json file to include actual objects and not ID's
Change data model to use int's and not objects
But I would very much like not to go that way, the first one requiring an insane amount of tedious work, and the other one forcing me to have 2 versions of pretty much the same objects, and then map the properties in between. That seems silly, surely I can't be the first person to face this.
What can I do to achieve my goal ?
You will want to clean this up a bit. But should give you a proof of concept on how to do create your custom converter.
public class Item
{
public int id { get; set; }
public string name { get; set; }
public int type { get; set; }
public double price { get; set; }
[JsonConverter(typeof(KeysJsonConverter))]
public List<Ingredient> ingredients { get; set; }
}
public class RootObject
{
public List<Item> items { get; set; }
}
public class KeysJsonConverter : JsonConverter
{
public KeysJsonConverter()
{
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanWrite is false. The type will skip the converter.");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var ingredientsList = new List<Ingredient>();
if (reader.TokenType != JsonToken.Null)
{
if (reader.TokenType == JsonToken.StartArray)
{
JToken token = JToken.Load(reader);
List<int> items = token.ToObject<List<int>>();
ingredientsList = items.Select(x => IngredientList.Ingredients.FirstOrDefault(y => y.Id == x)).ToList();
}
}
return ingredientsList;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(object[]);
}
}
public static class IngredientList
{
public static List<Ingredient> Ingredients = new List<Ingredient>()
{
new Ingredient()
{
Id = 1,
Name = "Test 1"
},
new Ingredient()
{
Id = 2,
Name = "Test 2"
}
};
}
public class Ingredient{
public string Name { get; set; }
public int Id { get; set; }
}

Cannot Deserialize JSON Object [duplicate]

This question already has answers here:
How to handle both a single item and an array for the same property using JSON.net
(9 answers)
Closed 4 years ago.
My problem is that I'm getting this error
"Cannot deserialize the current JSON object (e.g. {\"name\":\"value\"}) into type 'System.Collections.Generic.List`1[api.Controllers.Journey]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly" ... When I try to serialize my API json response.
The API return something like this:
{
"Transport": [
{
"$id": "6",
"SourceID": "1",
"Context": "1",
"Id": "AMADEUS#1",
"Provider": null,
"Journey": {
"SourceID": "R1G0S0",
"Duration": "42000",
"Id": "5b6db9c6bfac4"
}
},
{
"$id": "7",
"SourceID": "1",
"Context": "1",
"Id": "AMADEUS#1",
"Provider": null,
"Journey": [
{
"SourceID": "R1G0S0",
"Duration": "42000",
"Id": "5b6db9c6bfac4"
},
{
"SourceID": "R1G0S1",
"Duration": "42000",
"Id": "5b6db9c6bsac4"
}
]
}
]
}
The Journey field is an JObject at first result, but is an JArray on second...
And I'm getting the error when I deserialize:
Transport Transport = JsonConvert.DeserializeObject<Transport>(json_response);
My class properties:
public class Transport{
public string SourceID { get; set; }
public string Context { get; set; }
public string Id { get; set; }
public ProviderOD Provider { get; set; }
public Journey[] Journey { get; set; }
public PriceOD Price { get; set; }
}
public class Journey
{
public string Id { get; set; }
public string SourceID { get; set; }
public string Duration { get; set; }
}
What should I do, to set Journey[ ] or Journey dynamically ??
Thanks in advance ...
Your class declaration is not right, you have to use the following to decode
TransportRootObject Transport = JsonConvert.DeserializeObject<TransportRootObject>(json_response);
and this class(es) declaration
public class TransportRootObject
{
public List<Transport> Transport { get; set; }
}
public class Transport
{
[JsonProperty("$id")]
public string Id { get; set; }
public string SourceID { get; set; }
public string Context { get; set; }
public string Id { get; set; }
public ProviderOD Provider { get; set; }
public List<Journey> Journey { get; set; }
}
public class Journey
{
public string Id { get; set; }
public string SourceID { get; set; }
public string Duration { get; set; }
}
I think your issue is that you've got poorly formatted JSON being returned from your API. If Journey is an array, it should always return as an array/list...not an object if there is only 1 and then an array if there is more than one.
If this is a third party API that you are unable to fix then your next option is to use a custom converter to handle weirdly formatted json like this.
I don't recall where i found the original source/solution for this but I'll walk you through the steps.
First you'll need to create a new class such as this.
public class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
This is your custom converter to handle a "sometimes array, sometimes object" situation. It pretty much just does a test to determine if its an array or an object and handles accordingly.
Then, within your transport class, you'll add the attribute above the Journey array property like so.
[JsonConverter(typeof(SingleOrArrayConverter<Journey>))]
public List<Journey> Journey { get; set; }
This should then handle situations where journey comes through as an array or as an object.
So there's a few issues here. Your top level Transport object in your JSON is actually an array of transport objects. So you need an extra class:
public class DocumentRoot
{
public List<Transport> Transport { get; set; }
}
Then you want:
var DocumentRoot = JsonConvert.DeserializeObject<DocumentRoot>(json_response);
//DocumentRoot.Transport is now a List<Transport> of your transport elements from the document.
In your JSON one of the member names is "$id" which of course isn't valid in C#, so to make this behave you need to add a [JsonProperty(PropertyName = "$id")] attribute on your Id property to tell the serializer the name.

How to cleanly deserialize JSON where string values are wrapped in objects of the same name

I want to deserialize some strange JSON to C# classes:
{
"Result": {
"Client": {
"ProductList": {
"Product": [
{
"Name": {
"Name": "Car polish"
}
}
]
},
"Name": {
"Name": "Mr. Clouseau"
},
"AddressLine1": {
"AddressLine1": "Hightstreet 13"
}
}
}
}
json2csharp generates the following classes for the JSON:
public class Name
{
public string Name { get; set; }
}
public class Product
{
public Name Name { get; set; }
}
public class ProductList
{
public List<Product> Product { get; set; }
}
public class Name2
{
public string Name { get; set; }
}
public class AddressLine1
{
public string AddressLine1 { get; set; }
}
public class Client
{
public ProductList ProductList { get; set; }
public Name2 Name { get; set; }
public AddressLine1 AddressLine1 { get; set; }
}
public class Result
{
public Client Client { get; set; }
}
public class RootObject
{
public Result Result { get; set; }
}
The problem is that the duplicated property names in the objects (Name in Product and Client, AddressLine1 in Client) forces me to create an extra class with only one string property (Name, AddressLine1) to be able to deserialize the JSON.
The generated code is also invalid, because member names cannot be the same as their enclosing type (but I know that can be solved using the [JsonProperty(PropertyName = "Name")] attribute).
What's the best way to avoid that unnecessary level in the class hierarchy and have a clean class structure to be able to deserialize this JSON using JSON.NET? Note this is a third-party API, so I can't just change the JSON.
Indeed, this is a strange format for an API result, making it more difficult to consume. One idea to solve the problem is to create a custom JsonConverter that can take a wrapped value and return the inner value as if the wrapper were not there. This would allow you to deserialize the clunky JSON into a more sensible class hierarchy.
Here is a converter that should work:
class WrappedObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
// Get the value of the first property of the inner object
// and deserialize it to the requisite object type
return token.Children<JProperty>().First().Value.ToObject(objectType);
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Armed with this converter, you can create a class hierarchy that eliminates the extra levels of nesting. You must mark the properties that need to be "unwrapped" with a [JsonConverter] attribute so that Json.Net knows when to apply the custom converter. Here is the improved class structure:
public class RootObject
{
public Result Result { get; set; }
}
public class Result
{
public Client Client { get; set; }
}
public class Client
{
[JsonConverter(typeof(WrappedObjectConverter))]
public List<Product> ProductList { get; set; }
[JsonConverter(typeof(WrappedObjectConverter))]
public string Name { get; set; }
[JsonConverter(typeof(WrappedObjectConverter))]
public string AddressLine1 { get; set; }
}
public class Product
{
[JsonConverter(typeof(WrappedObjectConverter))]
public string Name { get; set; }
}
(Note that if the Result object will not contain any other properties besides Client, you can apply the WrappedObjectConverter there as well to move the Client up to the RootObject and eliminate the Result class.)
Here is a demo showing the converter in action:
class Program
{
static void Main(string[] args)
{
string json = #"
{
""Result"": {
""Client"": {
""ProductList"": {
""Product"": [
{
""Name"": {
""Name"": ""Car polish""
}
}
]
},
""Name"": {
""Name"": ""Mr. Clouseau""
},
""AddressLine1"": {
""AddressLine1"": ""Hightstreet 13""
}
}
}
}";
RootObject obj = JsonConvert.DeserializeObject<RootObject>(json);
Client client = obj.Result.Client;
foreach (Product product in client.ProductList)
{
Console.WriteLine(product.Name);
}
Console.WriteLine(client.Name);
Console.WriteLine(client.AddressLine1);
}
}
Output:
Car polish
Mr. Clouseau
Hightstreet 13
It sounds like you may be interesting in implementing a custom JsonConverter. Here's a site that has some samples of how you could do this. It's a fairly simple process and would allow you to keep the JSON you're stuck with while having whatever class structure you're most comfortable with.

Categories