I have a DTO which is an abstract class. When I am calling the Web API, I need to pass the child class. I followed this link Binding abstract action parameters in WebAPI but what I am not getting is where to put
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(
new PolymorphicProductConverter()
);
This code in my ASP.NET Core application.
Can anyone please help?
My models are like this
public abstract class HrBuildingComponentV1DTO
{
public HrBuildingComponentV1DTO() { }
public string Id { get; set; }
public string Label { get; set; }
public BuildingComponentType Type { get; set; }
public int? Index { get; set; }
public bool IsFilter { get; set; }
public string SectionId { get; set; }
public HrBuildingComponentV1DTO(HrBuildingComponentsV1 component)
{
Id = component.Id;
Label = component.Label;
Type = component.Type;
Index = component.Index;
IsFilter = component.IsFilter;
SectionId = component.SectionId;
}
public abstract HrBuildingComponentsV1 ToModel();
}
public class HrTextBuildingComponentV1DTO : HrBuildingComponentV1DTO
{
public string Value { get; set; }
public HrTextBuildingComponentV1DTO() : base() { }
public HrTextBuildingComponentV1DTO(HrTextBuildingComponentV1 model) : base(model)
{
Value = model.Value;
}
public override HrBuildingComponentsV1 ToModel()
{
return new HrTextBuildingComponentV1(this);
}
}
Here is my custom converter
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
namespace HumanRisks.API.Helpers
{
public class PolymorphicHrBuildingComponentConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(HrBuildingComponentV1DTO);
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
var obj = JObject.Load(reader);
HrBuildingComponentV1DTO product;
var pt = obj["Type"];
if (pt == null)
{
throw new ArgumentException("Missing productType", "productType");
}
BuildingComponentType productType = pt.Value<BuildingComponentType>();
if (productType == BuildingComponentType.Text)
{
product = new HrTextBuildingComponentV1DTO();
}
else if (productType == BuildingComponentType.TextArea)
{
product = new HrTextAreaBuildingComponentV1DTO();
}
else
{
throw new NotSupportedException("Unknown product type: " + productType);
}
serializer.Populate(obj.CreateReader(), product);
return product;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
Related
I am stuck at a problem with this, my current base API class implements getting an object from an API like this:
HttpResponceMessage GetResponce(string path) => Task.Run(async() => await client.GetAsync(path));
T GetTFromRequest<T>(string path, JsonConverter[] converters) =>
Task.Run(async() => await GetResponce(path).Content.ReadAsAsync<T>(converters); // Converters gets put into a new JsonMediaTypeFormatter
These are used in other classes to make quick methods. Class Example:
public class ExampleAPI : BaseAPI
{
private static readonly JsonConverter[] converters = new[] { new RecordCoverter() };
public string APIKey { get; set; }
public ExampleData GetExampleData(/* params here such as string apiKey */) =>
GetTFromRequset<ExampleData>($"examplepath?key={APIKey}", converters);
}
Example data would be something like this:
{
"success":true,
"records":[
{
"Foo":"Bar",
"Data":"Dahta"
}
]
}
So the classes would look like this:
public class RecordConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(IRecord);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public interface IRecord {}
public class ExampleData
{
public bool success { get; set; } = true;
public IRecord[] records { get; set; } = new[] {};
}
public class DataRecord : IRecord
{
public string Foo { get; set; }
public string Data { get; set; }
}
public class FaltyRecord : IRecord
{
public string Bar { get; set; }
public string Dahta { get; set; }
}
The issue that keeps arising that I can't figure out if the objects in "records" is FaltyRecords or DataRecords without doing reader.Read() which would just throw off just being about to put the following into RecordConverter:
if(reader.NextProperties().All(x => DataRecordProps.Contains(x)))
return serializer.Deserialize<DataExample>(reader);
if(reader.NextProperties().All(x => FaltyRecordProps.Contains(x)))
return serializer.Deserialize<FaltyRecord>(reader);
if(...)
return ...;
I know that in XmlReader there's the method GetSubtree() so I can do var propReader = reader.GetSubtree(); then use propReader as much as I want and just do retrun (IRecord[])serializer.Deserialize(reader);. I also see using JObject a lot but I don't see a JObject(reader) method.
Use JObject.Load(reader) method.
var jObj = JObject.Load(reader);
var properties = jObj.Properties();
// code here to find the type
return jObj.ToObject(type);
I know there are many similar questions on SO, however all the ones I've found require a shared base class in order to work.
With a stream of JSON data like this:
[
{
"webhookType": "order",
"data": {
"id": "eeiefj393",
"orderProperty": "Value"
}
},
{
"webhookType": "customer",
"data": {
"id": 29238,
"customerProperty": "Value"
}
}
]
I wish to deserialize this into two containers, List<Customer> and List<Order>. Where the two classes are as follows:
class Order
{
public string Id { get; set; }
public string OrderProperty { get; set; }
[...]
}
class Customer
{
public long Id { get; set; }
public string CustomerProperty { get; set; }
[...]
}
There may be shared property names however there are no shared properties + type between these two classes and so the solutions involing a sub class aren't working for me.
You need to create a JsonConverter.
DataConverter
public class WebHookConverter : 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)
{
if (reader.TokenType == JsonToken.StartObject)
{
JObject item = JObject.Load(reader);
if (item["webhookType"].Value<string>() == "order")
{
var webhook = new WebHook
{
Type = item["webhookType"].Value<string>(),
Data = item["data"].ToObject<Order>()
};
return webhook;
}
else if (item["webhookType"].Value<string>() == "customer")
{
var webhook = new WebHook
{
Type = item["webhookType"].Value<string>(),
Data = item["data"].ToObject<Customer>()
};
return webhook;
}
}
return null;
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
Objects
[JsonConverter(typeof(WebHookConverter))]
public class WebHook
{
[JsonProperty("webhookType")]
public string Type { get; set; }
public object Data { get; set; }
}
public class Order
{
public string Id { get; set; }
[JsonProperty("orderProperty")]
public string Property { get; set; }
}
public class Customer
{
public long Id { get; set; }
[JsonProperty("customerProperty")]
public string Property { get; set; }
}
Serialization
var json = File.ReadAllText("json1.json");
var obj = JsonConvert.DeserializeObject<List<WebHook>>(json);
var orderList = obj.Where(o => o.Type == "order").Select(o => o.Data).ToList();
var customerList = obj.Where(o => o.Type == "customer").Select(o => o.Data).ToList();
Output:
I create a class for define my request, I don't get the accepted JSON string
I define this object:
public class Request
{
public Var_Args[] var_args { get; set; }
}
public class Var_Args
{
public object title { get; set; }
public object owner { get; set; }
}
when I convert it to json, I get the following string:
{"requests":[{"var_args":[{"title":"Test","owner":"skaner"}]}]}
how can I define the class, for get the accepted json string:
{"requests":[{"var_args":[{"title":"Test"},{"owner":"skaner"}]}]}
You can write a custom JSON converter that can serialize every property of an object (of a known type) into a different JSON object.
public class PropertyAsObjectConverter : JsonConverter
{
private readonly Type[] _types;
public PropertyAsObjectConverter(params Type[] types)
{
_types = types;
}
public override bool CanConvert(Type objectType)
{
return _types.Any(t => t == objectType);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var properties = value.GetType().GetProperties(BindingFlags.Public|BindingFlags.Instance);
foreach(var property in properties)
{
var name = property.Name;
var attrs = property.GetCustomAttributes(typeof(JsonPropertyAttribute));
if(attrs != null)
{
if (attrs.FirstOrDefault() is JsonPropertyAttribute attr)
name = attr.PropertyName;
}
writer.WriteStartObject();
writer.WritePropertyName(name);
serializer.Serialize(writer, property.GetValue(value));
writer.WriteEndObject();
}
}
}
This implements only the serialization, but you can extend it to support deserialization too. You can also extend it to serialize fields should you need that.
You can then define your classes as follows. Notice that I am using JsonPropertyAttribute here to specify the name in the serialized JSON.
public class Content
{
[JsonProperty("requests")]
public Request Value { get; set; }
}
public class Request
{
[JsonProperty("var_args")]
public VarArgs[] Arguments { get; set; }
}
public class VarArgs
{
[JsonProperty("title")]
public object Title { get; set; }
[JsonProperty("owner")]
public object Owner { get; set; }
}
This is how you can use it:
static void Main(string[] args)
{
var request = new Content()
{
Value = new Request()
{
Arguments = new VarArgs[]
{
new VarArgs()
{
Title = "Test",
Owner = "Skaner",
}
}
}
};
var text = JsonConvert.SerializeObject(
request,
Formatting.None,
new PropertyAsObjectConverter(typeof(VarArgs)));
Console.WriteLine(text);
}
The output for this sample is the one you expect:
{"requests":{"var_args":[{"title":"Test"},{"owner":"Skaner"}]}}
You could use a custom JsonConverter like the below.
It takes the Var_Args object and splits it in two different JObject which correspond to two different JSON objects.
public class VarArgsConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var obj = (JObject)JToken.FromObject(value);
var objTitle = new JObject();
objTitle.Add("title", obj.GetValue("title"));
var objOwner = new JObject();
objOwner.Add("owner", obj.GetValue("owner"));
objTitle.WriteTo(writer);
objOwner.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 objectType == typeof(Var_Args);
}
}
public class Wrapper
{
[JsonProperty("requests")]
public Request Requests { get; set; }
}
public class Request
{
public Var_Args[] var_args { get; set; }
}
public class Var_Args
{
public object title { get; set; }
public object owner { get; set; }
}
Then use it:
var wrapper = new Wrapper();
var request = new Request();
request.var_args = new Var_Args[] {
new Var_Args(){ title = "Test", owner = "skaner" },
new Var_Args(){ title = "Test2", owner = "skaner2" }
};
wrapper.Requests = request;
var serialized = JsonConvert.SerializeObject(wrapper, new VarArgsConverter());
Output
{"requests":{"var_args":[{"title":"Test"},{"owner":"skaner"},{"title":"Test2"},{"owner":"skaner2"}]}}
Note: I'm using the Wrapper class just to produce the requested JSON.
If you don't want to specify the converter each time, you can register your converter globally. Please see this answer which explains how you can do that. So, the serializer will use your custom JsonConverter every time you try to serialize a Var_Args object.
If you register the JsonConvert globally you can use:
var serialized = JsonConvert.SerializeObject(wrapper);
You can use System.Reflection to redefine Var_Args as an implementation of the IEnumerable<Dictionary<string,object>> interface by adding two methods to the class:
public class Var_Args : IEnumerable<Dictionary<string,object>>
{
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public IEnumerator<Dictionary<string,object>> GetEnumerator()
{
var Properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var Property in Properties)
{
var Entry = new Dictionary<string,object>();
Entry.Add(Property.Name, Property.GetValue(this));
yield return Entry;
}
}
public object title { get; set; }
public object owner { get; set; }
}
While Reflection may be regarded as slow, there is a technique you can use to statically compile an IEnumerable at runtime so that the reflection only occurs once for the definition of the class, like this:
public class Var_Args : IEnumerable<Dictionary<string,object>>
{
private struct PropertyList<T>
{
public static readonly List<Func<T,Dictionary<string,object>>> PropertyGetters;
static PropertyList()
{
PropertyGetters = new List<Func<T,Dictionary<string,object>>>();
var Properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var Property in Properties)
{
var Args = new [] { Expression.Parameter(typeof(T)) };
var Key = Property.Name;
var Value = Expression.Property(Args[0], Property);
Func<T,object> Get = Expression.Lambda<Func<T,object>>(Value, Args).Compile();
PropertyGetters.Add(obj =>
{
var entry = new Dictionary<string,object>();
entry.Add(Key, Get(obj));
return entry;
});
}
}
}
protected static IEnumerable<Dictionary<string,object>> GetPropertiesAsEntries<T>(T obj)
{
return PropertyList<T>.PropertyGetters.Select(f => f(obj));
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public IEnumerator<Dictionary<string,object>> GetEnumerator()
{
return GetPropertiesAsEntries(this).GetEnumerator();
}
public object title { get; set; }
public object owner { get; set; }
}
Say I have some Json that will come in a packet like this:
{
"LinkType1": "google",
"LinkUrl1": "https://plus.google.com/test",
"LinkShow1": 1,
"LinkType2": "facebook",
"LinkUrl2": "https://www.facebook.com/test",
"LinkShow2": 0,
"LinkType3": "linkedin",
"LinkUrl3": "http://www.linkedin.com/test",
"LinkShow3": 1,
"count": 3,
"errorCode": 0,
"errorMessage": "Success"
}
Notice how everything comes back as the same property, but with an index on it?
I would love to be able to deserialize that data as though it was an array instead of single properties. What would be the best method for deserializing this into the classes below? I'm using the Newtonsoft Json library for serialization, so a solution using that would be preferred.
public class LinksResult
{
public List<LinkData> Links { get; set; }
[JsonProperty("count")]
public int Count { get; set; }
[JsonProperty("errorCode")]
public int ErrorCode { get; set; }
[JsonProperty("errorMessage")]
public string ErrorMessage { get; set; }
}
public class LinkData
{
public string LinkType { get; set; }
public string LinkUrl { get; set; }
public bool LinkShow { get; set; }
}
You can use a custom JsonConverter to deserialize the JSON data into the structure that you want. Here is what the code for the converter might look like.
class LinksResultConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(LinksResult));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
LinksResult result = new LinksResult();
result.Count = (int)obj["count"];
result.ErrorCode = (int)obj["errorCode"];
result.ErrorMessage = (string)obj["errorMessage"];
result.Links = new List<LinkData>();
for (int i = 1; i <= result.Count; i++)
{
string index = i.ToString();
LinkData link = new LinkData();
link.LinkType = (string)obj["LinkType" + index];
link.LinkUrl = (string)obj["LinkUrl" + index];
link.LinkShow = (int)obj["LinkShow" + index] == 1;
result.Links.Add(link);
}
return result;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the converter, just add a [JsonConverter] attribute to your LinksResult class as shown below. (Note that you don't need the [JsonProperty] attributes with this approach, since the mapping between JSON property names and the actual class members is handled directly by the converter.)
[JsonConverter(typeof(LinksResultConverter))]
public class LinksResult
{
public List<LinkData> Links { get; set; }
public int Count { get; set; }
public int ErrorCode { get; set; }
public string ErrorMessage { get; set; }
}
Then, you can deserialize like this:
LinksResult result = JsonConvert.DeserializeObject<LinksResult>(json);
Fiddle: https://dotnetfiddle.net/56b34H
Brian's answer was very good and it got me 80% of the way to where I wanted to be. However it's not a very good implementation to use over and over again if this sort of pattern happens on many different objects.
I made something more generic. An interface that a "Page" would have.
public interface IPage<TItem>
{
int Count { get; set; }
List<TItem> PageItems { get; set; }
}
Then the Page converter itself.
public class PageConverter<TPage, TItem> : JsonConverter
where TPage : IPage<TItem>, new()
where TItem : new()
{
private readonly Regex _numberPostfixRegex = new Regex(#"\d+$");
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(TPage));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var obj = serializer.Deserialize<JObject>(reader);
var page = new TPage();
serializer.Populate(obj.CreateReader(), page); //Loads everything that isn't a part of the items.
page.PageItems = new List<TItem>();
for (int i = 1; i <= page.Count; i++)
{
string index = i.ToString();
//Find all properties that have a number at the end, then any of those that are the same number as the current index.
//Put those in a new JObject.
var jsonItem = new JObject();
foreach (var prop in obj.Properties().Where(p => _numberPostfixRegex.Match(p.Name).Value == index))
{
jsonItem[_numberPostfixRegex.Replace(prop.Name, "")] = prop.Value;
}
//Deserialize and add to the list.
TItem item = jsonItem.ToObject<TItem>(serializer);
page.PageItems.Add(item);
}
return page;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
So then all that's needed is to implement it on the links result:
[JsonConverter(typeof(PageConverter<LinksResult, LinkData>))]
public class LinksResult : IPage<LinkData>
{
public int Count { get; set; }
public List<LinkData> PageItems { get; set; }
}
I figured out you can control the serialization of capitalization with JsonSerializerSettings, so best leave that detail up to the chosen serializer, not my converter.
Fiddle here: https://dotnetfiddle.net/7KhwYY
Here is similar solution you may apply. See Serialize json to an object with catch all dictionary property The answer by David Hoerster.
I have a problem with deserializing some JSON string into c# objects.
Let's assume i have the following code:
public class A
{
public string Name { get; set; }
}
public class B : A
{
public int Age { get; set; }
}
public class C
{
public ObservableCollection<A> As
{
get;
set;
}
}
And i serialize the following object _c:
C _c = new C();
_c.As.Add(new B() { Name = "Max", Age = 4 });
This works pretty well.
But i am not able to deserialize the created JSON-string and create instance of B, because the ObservableCollection is declared with A as generic type. Is there some way to tell Newtonsoft.Json to use B instead of A?
Thank you very much.
Seems like you solved it but you can achieve same thing by changing your class C constraint, without using any custom JSON converters. See below:
public class C<T> where T : A
{
private ObservableCollection<T> _as = new ObservableCollection<T>();
public ObservableCollection<T> As
{
get { return _as; }
set { _as = value; }
}
}
And then de/serialize like
var _c = new C<B>();
_c.As.Add(new B() { Name = "Max", Age = 4 });
var serialized = JsonConvert.SerializeObject(_c);
var deserialized = JsonConvert.DeserializeObject<C<B>>(serialized); // B
Solved the problem with writing a custom JsonConverter:
public class BCJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var currentType = (existingValue as System.Collections.ObjectModel.ObservableCollection<A>);
currentType.Clear();
var des = serializer.Deserialize<IList<B>>(reader);
foreach (var toAdd in des)
{
currentType.Add(toAdd);
}
return currentType;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
And
public class C
{
public virtual ObservableCollection<A> As
{
get;
set;
}
}
public class D : C
{
[Newtonsoft.Json.JsonConverter(typeof(BCJsonConverter))]
public override ObservableCollection<A> As
{
get;
set;
}
}