How to bind a Polymorphic Properties of a Models in .NET core - asp.net

I have an ASP.NET Core Web API end point which takes (FromBody) The Search object defined below
public class Search {
public int PageSize {get;set;}
public Expression Query{get;set;}
}
public class Expression {
public string Type {get;set;}
}
public class AndExpression {
public IList<Expression> Expressions {get;set;}
}
public class MatchesExpression {
public string FieldId {get;set;}
public string Value {get;set;}
public string Operator {get;set;}
}
So... if I post the following JSON to my endpoint
{ "pageSize":10, "query": { "fieldId": "body", "value": "cake", "operator": "matches" } }
I successfully get a Search Object, but the Query property is of type Expression, not MatchesExpression.
This is clearly a polymorphic issue.
This article (towards the end) gives a good example of a how to deal with this issue when your entire model is polymorphic.
https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-5.0
In my case, the property of my Model "Query" is polymorphic, so Im unsure how to build a ModelBinder for my Search object that will allow me to handle the Query Property
I Imagine, I need to write a model binder to construct the search object and then follow the pattern described for the property, however I cannot locate any examples of how to implement a model binder that isnt utterly trivial.
Any suggestions on how to achieve this? Good sources of information?

So.. I gave up with ModelBInders (because Im using the FromBody attribute which isnt compatible with my aims).
Instead I wrote a System.Text.Json JsonConvertor to handle the polymorphism (see shonky code below)
using Searchy.Models;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace Searchy
{
public class ExpressionJsonConverter : JsonConverter<Expression>
{
public override Expression Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Utf8JsonReader readerClone = reader;
using (var jsonDocument = JsonDocument.ParseValue(ref readerClone))
{
if (!jsonDocument.RootElement.TryGetProperty("type", out var typeProperty))
{
throw new JsonException();
}
switch (typeProperty.GetString())
{
case "comparison":
return JsonSerializer.Deserialize<Comparison>(ref reader, options);
case "and":
return JsonSerializer.Deserialize<And>(ref reader, options);
}
}
return null;
}
public override void Write(
Utf8JsonWriter writer,
Expression expression,
JsonSerializerOptions options)
{
}
}
}
My Expression class also had the following attribue
[JsonConverter(typeof(ExpressionJsonConverter))]

I recently ran into the same issue, but was using Newtonsoft.Json, here is iasksillyquestions' solution using Newtonsoft.Json:
public class ConnectionJsonConverter : JsonConverter<Connection>
{
public override Connection? ReadJson(JsonReader reader, Type objectType, Connection? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
// if this is the base type, deserialize it into the child type
if (objectType == typeof(Connection))
{
JObject parsedObject = JObject.Load(reader);
if (!parsedObject.TryGetValue("connectionType", out var connectionType))
{
throw new Exception("Unable to parse ConnectionType");
}
switch (connectionType.ToObject<ConnectionType>())
{
case ConnectionType.Database:
return parsedObject.ToObject<DatabaseConnection>();
case ConnectionType.LDAP:
return parsedObject.ToObject<LDAPConnection>();
default:
throw new Exception("Unrecognised ConnectionType");
}
}
// if it is a child type, just deserialize it normally
else
{
serializer.ContractResolver.ResolveContract(objectType).Converter = null;
return serializer.Deserialize(reader, objectType) as Connection;
}
}
public override void WriteJson(JsonWriter writer, Connection? value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
In my case I had an enum ConnectionType to specify what derived type the object was.
Connection.cs:
[JsonConverter(typeof(ConnectionJsonConverter))]
public abstract class Connection : Entity
{
#region Properties
// what type of system are we connecting to? database / api / ldap ...
public ConnectionType ConnectionType { get; set; }
// an optional description to explain where the connection is going
public string Description { get; set; } = "";
#endregion
#region Helper Methods
// test if the connection is good. Will throw an error if the connection is not good.
public abstract Task Test();
#endregion
}
LDAPConnection.cs:
public class LDAPConnection : Connection
{
#region Properties
public string Endpoint { get; set; }
public string? Username { get; set; }
public string? Password { get; set; }
#endregion
#region Helper Methods
public override async Task Test()
{
DirectoryEntry directoryEntry = new DirectoryEntry(this.Endpoint, this.Username, this.Password);
// check if the native object exists. If it does, then we are connected
if (directoryEntry.NativeObject == null)
{
throw new Exception("Unable to bind to server");
}
// assume that if we got here without any exceptions, we are connected.
}
#endregion
}
Then you can have one controller for all derived types:
// POST: /api/connections/create
[HttpPost, ActionName("Create")]
public async Task<IActionResult> Create([Bind] Connection connection)
{
connection.Test();
}

Related

A durable entity does not deserialize

I am trying to use a durable entity in my Azure Function to cache some data. However, when I try to retrieve the entity (state) for the first time, I get an exception indicating an issue during the entity deserialization.
Here is my entity class and related code
[JsonObject(MemberSerialization.OptIn)]
public class ActionTargetIdCache : IActionTargetIdCache
{
[JsonProperty("cache")]
public Dictionary<string, ActionTargetIdsCacheItemInfo> Cache { get; set; } = new Dictionary<string, ActionTargetIdsCacheItemInfo>();
public void CacheCleanup(DateTime currentUtcTime)
{
foreach (string officeHolderId in Cache.Keys)
{
TimeSpan cacheItemAge = currentUtcTime - Cache[officeHolderId].lastUpdatedTimeStamp;
if (cacheItemAge > TimeSpan.FromMinutes(2))
{
Cache.Remove(officeHolderId);
}
}
}
public void DeleteActionTargetIds(string officeHolderId)
{
if (this.Cache.ContainsKey(officeHolderId))
{
this.Cache.Remove(officeHolderId);
}
}
public void DeleteState()
{
Entity.Current.DeleteState();
}
public void SetActionTargetIds(ActionTargetIdsCacheEntry entry)
{
this.Cache[entry.Key] = entry.Value;
}
public Task<ActionTargetIdsCacheItemInfo> GetActionTargetIdsAsync(string officeHolderId)
{
if (this.Cache.ContainsKey(officeHolderId))
{
return Task.FromResult(Cache[officeHolderId]);
}
else
{
return Task.FromResult(new ActionTargetIdsCacheItemInfo());
}
}
// public void Reset() => this.CurrentValue = 0;
// public int Get() => this.CurrentValue;
[FunctionName(nameof(ActionTargetIdCache))]
public static Task Run([EntityTrigger] IDurableEntityContext ctx)
=> ctx.DispatchAsync<ActionTargetIdCache>();
}
public class ActionTargetIdsCacheEntry
{
// officeHolderId
public string Key { get; set; } = string.Empty;
public ActionTargetIdsCacheItemInfo Value { get; set; } = new ActionTargetIdsCacheItemInfo();
}
[JsonObject(MemberSerialization.OptIn)]
public class ActionTargetIdsCacheItemInfo : ISerializable
{
public ActionTargetIdsCacheItemInfo()
{
lastUpdatedTimeStamp = DateTime.UtcNow;
actionTargetIds = new List<string>();
}
public ActionTargetIdsCacheItemInfo(SerializationInfo info, StreamingContext context)
{
lastUpdatedTimeStamp = info.GetDateTime("lastUpdated");
actionTargetIds = (List<string>)info.GetValue("actionTargetIds", typeof(List<string>));
}
[JsonProperty]
public DateTimeOffset lastUpdatedTimeStamp { get; set; } = DateTimeOffset.UtcNow;
[JsonProperty]
public List<string> actionTargetIds { get; set; } = new List<string>();
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("lastUpdated", lastUpdatedTimeStamp);
info.AddValue("actionTargetIds", actionTargetIds);
}
}
public interface IActionTargetIdCache
{
void CacheCleanup(DateTime currentUtcTime);
void DeleteActionTargetIds(string officeHolderId);
void DeleteState();
void SetActionTargetIds(ActionTargetIdsCacheEntry item);
// Task Reset();
Task<ActionTargetIdsCacheItemInfo> GetActionTargetIdsAsync(string officeHolderId);
// void Delete();
}
Here is the exception I get during the first attempt to access the state from an orchestration using the GetActionTargetIdsAsync method:
Exception has occurred: CLR/Microsoft.Azure.WebJobs.Extensions.DurableTask.EntitySchedulerException
Exception thrown: 'Microsoft.Azure.WebJobs.Extensions.DurableTask.EntitySchedulerException' in System.Private.CoreLib.dll: 'Failed to populate entity state from JSON: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'PolTrack.CdbGetFunctionApp.ActionTargetIdsCacheItemInfo' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path 'cache.officeHolderId1', line 1, position 29.'
Inner exceptions found, see $exception in variables window for more details.
Innermost exception Newtonsoft.Json.JsonSerializationException : Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'PolTrack.CdbGetFunctionApp.ActionTargetIdsCacheItemInfo' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path 'cache.officeHolderId1', line 1, position 29.
Could someone with the sufficient SO privileges please add the tag azure-durable-entities.
I did manage to get around this by following #silent suggestion. I re-designed the entity class to only use CLR types. In my case, that meant replacing Dictionary<string, ActionTargetIdsCacheItemInfo> with two dictionaries Dictionary<string, List<string>> and Dictionary<string, DateTimeOffset>.

Creating a custom converter to write json as a Dictionary<string, IObject> from an object of String Key IObject Value

I am trying to create a custom JsonConverter to follow a third party API with a rather complicated object structure, and am a bit hamstrung on a few things. Note I am using .NET 4.7.2, not Core.
I have source objects that look like this, which I have created as objects rather than dictionaries in order to easily integrate them with FluentValidation. The objects will represent json objects in the foreign API. The "Value" is an interfaced item which is eventually converted to one of about 20 concrete types.
public class PDItem : IDictionaryObject<List<IItem>>
{
[JsonProperty]
public string Key { get; set; }
[JsonProperty]
public List<IItem> Value { get; set; }
}
The source json for such an object is a dictionary, whereby the Key is the property, and the value is one of the 20 object types, as so:
{
"checked": [
{
"elementType": "input",
"inputType": "checkbox",
"name": "another_checkbox",
"label": "Another checkbox",
"checked": true
}
]
}
In this case, the object in C# would look something like this:
PDItem item = new PDItem() {
Key = "checked",
Value = new List<IItem>() {
new ConcreteTypeA () {
elementType = "input",
inputType = "checkbox",
name = "another_checkbox",
label = "Another checkbox",
#checked = true
}
}
};
For reference, my "PDItem"s implement the following interfaces:
public interface IDictionaryObject<T>
{
string Key { get; set; }
T Value { get; set; }
}
[JsonConverter(typeof(IItemConverter))]
public interface IItem: IElement
{
}
[JsonConverter(typeof(ElementConverter))]
public interface IElement
{
string elementType { get; }
}
I was able to convert some concrete types (without having to do any tricky dictionary to object conversions), below is a working example of the ElementConverter attacked to my IElement interface (IItem uses the same pattern, and the same JsonCreationConverter class):
public class ElementConverter : JsonCreationConverter<IElement>
{
protected override IElement Create(Type objectType, JObject jObject)
{
//TODO: Add objects to ElementConverter as they come online.
switch (jObject["elementType"].Value<string>())
{
case ElementTypeDescriptions.FirstType:
return new FirstType();
case ElementTypeDescriptions.SecondType:
return new SecondType();
case ElementTypeDescriptions.ThirdType:
return new ThirdType();
case ElementTypeDescriptions.FourthType:
case ElementTypeDescriptions.FifthType:
default:
throw new NotImplementedException("This object type is not yet implemented.");
}
}
}
public abstract class JsonCreationConverter<T> : JsonConverter
{
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
try
{
var jObject = JObject.Load(reader);
var target = Create(objectType, jObject);
serializer.Populate(jObject.CreateReader(), target);
return target;
}
catch (JsonReaderException)
{
return null;
}
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
What makes my scenario so tricky is not that I am converting objects to dictionaries and back, but that my object values are other concrete objects (interfaced). I want to write a custom JsonConverter to serialize and deserialize these objects, but have no idea how to read and write the json in the methods below, let alone if what I am attempting to do is even possible. Any assistance would be greatly appreciated!
public class PDItemConverter: JsonConverter<PDItem>
{
public override void WriteJson(JsonWriter writer, PDItem value, JsonSerializer serializer)
{
/// DO STUFF
}
public override PDItem ReadJson(JsonReader reader, Type objectType, PDItem existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
/// DO STUFF
}
}
EDITED PER DBC's request:
Apologies for the complicated question DBC, and I greatly appreciate your time! Obviously, I'm a bit new to posting to stack overflow (long time lurker as they say).
Below is full working code that will run in .net fiddle (or in a console application if you simply add a namespace and json.net 12.x packages to a new .NET 4.7.2 console project). Second apologies that it is still a bit long and complicated, It is actually greatly simplified thanks to the omission of the Validation code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public class PDItem : IDictionaryObject<List<IItem>>
{
[JsonProperty]
public string Key { get; set; }
[JsonProperty]
public List<IItem> Value { get; set; }
}
public interface IDictionaryObject<T>
{
string Key { get; set; }
T Value { get; set; }
}
[JsonConverter(typeof(IItemConverter))]
public interface IItem : IElement
{
string itemType { get; }
}
[JsonConverter(typeof(ElementConverter))]
public interface IElement
{
string elementType { get; }
}
public class ElementConverter : JsonCreationConverter<IElement>
{
protected override IElement Create(Type objectType, JObject jObject)
{
//TODO: Add objects to ElementConverter as they come online.
switch (jObject["elementType"].Value<string>())
{
case ElementTypeDescriptions.FirstType:
return new FirstType();
case ElementTypeDescriptions.SecondType:
return new SecondType();
//case ElementTypeDescriptions.ThirdType:
// return new ThirdType();
//case ElementTypeDescriptions.FourthType:
//case ElementTypeDescriptions.FifthType:
default:
throw new NotImplementedException("This object type is not yet implemented.");
}
}
}
public class IItemConverter : JsonCreationConverter<IItem>
{
protected override IItem Create(Type objectType, JObject jObject)
{
switch (jObject["itemType"].Value<string>())
{
case ItemTypeDescriptions.FirstItemType:
return new FirstItemType();
case ItemTypeDescriptions.SecondItemType:
return new SecondItemType();
default:
throw new NotImplementedException("This object type is not yet implemented.");
}
}
}
/// <summary>
/// Used constants rather than an enum to allow for use in switch statements. Provided by third party to us to identify their classes across the API.
/// </summary>
public class ElementTypeDescriptions
{
public const string FirstType = "firstTypeId";
public const string SecondType = "secondTypeId";
public const string ThirdType = "thirdTypeId";
public const string FourthType = "fourthTypeId";
public const string FifthType = "fifthTypeId";
}
/// <summary>
/// Used constants rather than an enum to allow for use in switch statements. Provided by third party to us to identify their classes across the API.
/// </summary>
public class ItemTypeDescriptions
{
public const string FirstItemType = "firstItemTypeId";
public const string SecondItemType = "secondItemTypeId";
}
/*** CONCRETE OBJECTS ***/
public class FirstType : IElement
{
public string elementType { get { return ElementTypeDescriptions.FirstType; } }
public string name { get; set; }
}
public class SecondType : IElement
{
public string elementType { get { return ElementTypeDescriptions.FirstType; } }
public string label { get; set; }
}
public class FirstItemType : IItem
{
public string elementType { get { return ElementTypeDescriptions.FourthType; } }
public string itemType { get { return ItemTypeDescriptions.FirstItemType; } }
public string reference { get; set; }
}
public class SecondItemType : IItem
{
public string elementType { get { return ElementTypeDescriptions.FourthType; } }
public string itemType { get { return ItemTypeDescriptions.FirstItemType; } }
public string database { get; set; }
}
/*** END CONCRETE OBJECTS ***/
public class PDItemConverter : JsonConverter<PDItem>
{
public override void WriteJson(JsonWriter writer, PDItem value, JsonSerializer serializer)
{
/// THIS CODE TO BE WRITTEN TO ANSWER THE QUESTION
/// DO STUFF
throw new NotImplementedException("THIS CODE TO BE WRITTEN TO ANSWER THE QUESTION");
}
public override PDItem ReadJson(JsonReader reader, Type objectType, PDItem existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
/// THIS CODE TO BE WRITTEN TO ANSWER THE QUESTION
/// DO STUFF
throw new NotImplementedException("THIS CODE TO BE WRITTEN TO ANSWER THE QUESTION");
}
}
public abstract class JsonCreationConverter<T> : JsonConverter
{
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
try
{
var jObject = JObject.Load(reader);
var target = Create(objectType, jObject);
serializer.Populate(jObject.CreateReader(), target);
return target;
}
catch (JsonReaderException)
{
return null;
}
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
class TestClass
{
public static void Test()
{
var json = GetJson();
var item = JsonConvert.DeserializeObject<PDItem>(json);
var json2 = JsonConvert.SerializeObject(item, Formatting.Indented);
Console.WriteLine(json2);
}
static string GetJson()
{
var json = #"{
""checked"": [
{
""elementType"": ""input"",
""inputType"": ""checkbox"",
""name"": ""another_checkbox"",
""label"": ""Another checkbox"",
""checked"": true
}
]
}
";
return json;
}
}
public class Program
{
public static void Main()
{
Console.WriteLine("Environment version: " + Environment.Version);
Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine();
try
{
TestClass.Test();
}
catch (Exception ex)
{
Console.WriteLine("Failed with unhandled exception: ");
Console.WriteLine(ex);
throw;
}
}
}
One of the easiest ways to write a JsonConverter is to map the object to be serialized to some DTO, then (de)serialize the DTO. Since your PDItem looks like a single dictionary key/value pair and is serialized like a dictionary, the easiest DTO to use would be an actual dictionary, namely Dictionary<string, List<IItem>>.
Thus your PDItemConverter can be written as follows:
public class PDItemConverter: JsonConverter<PDItem>
{
public override void WriteJson(JsonWriter writer, PDItem value, JsonSerializer serializer)
{
// Convert to a dictionary DTO and serialize
serializer.Serialize(writer, new Dictionary<string, List<IItem>> { { value.Key, value.Value } });
}
public override PDItem ReadJson(JsonReader reader, Type objectType, PDItem existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
// Deserialize as a dictionary DTO and map to a PDItem
var dto = serializer.Deserialize<Dictionary<string, List<IItem>>>(reader);
if (dto == null)
return null;
if (dto.Count != 1)
throw new JsonSerializationException(string.Format("Incorrect number of dictionary keys: {0}", dto.Count));
var pair = dto.First();
existingValue = hasExistingValue ? existingValue : new PDItem();
existingValue.Key = pair.Key;
existingValue.Value = pair.Value;
return existingValue;
}
}
Since you are (de)serializing using the incoming serializer any converters associated to nested types such as IItem will get picked up and used automatically.
In addition, in JsonCreationConverter<T> you need to override CanWrite and return false. This causes the serializer to fall back to default serialization when writing JSON as explained in this answer to How to use default serialization in a custom JsonConverter. Also, I don't recommend catching and swallowing JsonReaderException. This exception is thrown when the JSON file itself is malformed, e.g. by being truncated. Ignoring this exception and continuing can occasionally force Newtonsoft to fall into an infinite loop. Instead, propagate the exception up to the application:
public abstract class JsonCreationConverter<T> : JsonConverter
{
// Override CanWrite and return false
public override bool CanWrite { get { return false; } }
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var target = Create(objectType, jObject);
serializer.Populate(jObject.CreateReader(), target);
return target;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Demo fiddle here.

ASP.NET Web API returning C# class

In Web API Controller action method returns C# class, like that:
public class ShipController : ApiController
{
[HttpGet]
public Cell GetShip()
{
return new Cell(new ScriptEngine());
}
}
Where Cell is my class, inherited from ObjectInstance from Jurassic JS library. When I call this action, ASP.NET tries to serialize my object to XML or JSON, and I get System.InvalidOperationException: "The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'." I also tried to add [DataContract] attributes to class, as I found here, like that:
[DataContract]
public class Cell : ObjectInstance
{
[DataMember]
public int cellId;
public Cell(ScriptEngine engine) : base(engine)
{
}
}
But I still get error. How to make action return only my fields of class serialized, and not get into parent classes?
You want to serialize the Cell class only, not its parent class, right? The solution is: create a custom JSON converter. Here is the code:
Cell.cs
[JsonConverter(typeof(CellJsonConverter))]
public class Cell : Parent
{
public int CellId { get; set; }
public string Name { get; set; }
public Cell(int id) : base(id)
{
CellId = id;
Name = "This is a name";
}
}
CellJsonConverter.cs
public class CellJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var cell = (Cell) value;
writer.WriteStartObject();
writer.WritePropertyName("cellId");
serializer.Serialize(writer, cell.CellId);
writer.WritePropertyName("name");
serializer.Serialize(writer, cell.Name);
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Cell);
}
}
When you call your API, you will get: {"cellId":10,"name":"This is a name"}.
JsonConverter is from Newtonsoft.Json.
Feel free to ask if there's something unclear to you :)

JSON.Net - Change $type field to another name?

When using Json.Net, I understand how to get the $type property into the rendered json, but is there a way to change that field name? I need to use "__type" instead of "$type".
http://json.codeplex.com/workitem/22429
"I would rather keep $type hard coded and consistent."
Consistent with what I wonder?
http://json.codeplex.com/workitem/21989
I would rather not - I think this is too specific to me and I don't
want to go overboard with settings. At some point I will probably
implement this - http://json.codeplex.com/workitem/21856 - allowing
people to read/write there own meta properties in the JSON and you
could reimplement type name handling with a new property name. The
other option is just to modify the source code for yourself to have
that property name.
And more recently, Issue #36: Customizable $type property name feature:
I'd rather not
This is my solution...
json.Replace("\"$type\": \"", "\"type\": \"");
Looks like this is hardcoded as public const string TypePropertyName = "$type"; in Newtonsoft.Json.Serialization.JsonTypeReflector which is internal static class unfortunately.
I needed this myself, and the only thing I can think of is having custom modified version of json.net itself. Which is of course is a major pita.
when serializing, there is a nice way to override the property name:
public class CustomJsonWriter : JsonTextWriter
{
public CustomJsonWriter(TextWriter writer) : base(writer)
{
}
public override void WritePropertyName(string name, bool escape)
{
if (name == "$type") name = "__type";
base.WritePropertyName(name, escape);
}
}
var serializer = new JsonSerializer();
var writer = new StreamWriter(stream) { AutoFlush = true };
serializer.Serialize(new CustomJsonWriter(writer), objectToSerialize);
I haven't tried deserialization yet, but in worst case I could use:
json.Replace("\"__type": \"", "\"type\": \"$type\");
I had to do this for my UI REST API as Angular.js disregards fields names starting with a dollar sign ($).
So here's a solution that renames $type to __type for the whole Web API and works both for serialization and deserialization.
In order to be able to use a custom JsonWriter and a custom JsonReader (as proposed in the other answers to this question), we have to inherit the JsonMediaTypeFormatter and override the corresponding methods:
internal class CustomJsonNetFormatter : JsonMediaTypeFormatter
{
public override JsonReader CreateJsonReader(Type type, Stream readStream, Encoding effectiveEncoding)
{
return new CustomJsonReader(readStream, effectiveEncoding);
}
public override JsonWriter CreateJsonWriter(Type type, Stream writeStream, Encoding effectiveEncoding)
{
return new CustomJsonWriter(writeStream, effectiveEncoding);
}
private class CustomJsonWriter : JsonTextWriter
{
public CustomJsonWriter(Stream writeStream, Encoding effectiveEncoding)
: base(new StreamWriter(writeStream, effectiveEncoding))
{
}
public override void WritePropertyName(string name, bool escape)
{
if (name == "$type") name = "__type";
base.WritePropertyName(name, escape);
}
}
private class CustomJsonReader : JsonTextReader
{
public CustomJsonReader(Stream readStream, Encoding effectiveEncoding)
: base(new StreamReader(readStream, effectiveEncoding))
{
}
public override bool Read()
{
var hasToken = base.Read();
if (hasToken && TokenType == JsonToken.PropertyName && Value != null && Value.Equals("__type"))
{
SetToken(JsonToken.PropertyName, "$type");
}
return hasToken;
}
}
}
Of course you need to register the custom formatter in your WebApiConfig. So we replace the default Json.NET formatter with our custom one:
config.Formatters.Remove(config.Formatters.JsonFormatter);
config.Formatters.Add(new CustomJsonNetFormatter());
Done.
We had a need for this so I created a custom JsonReader. We're are using rest in our MS web services with complex data models and needed to replace the "__type" property with "$type."
class MSJsonReader : JsonTextReader
{
public MSJsonTextReader(TextReader reader) : base(reader) { }
public override bool Read()
{
var hasToken = base.Read();
if (hasToken && base.TokenType == JsonToken.PropertyName && base.Value != null && base.Value.Equals("__type"))
base.SetToken(JsonToken.PropertyName, "$type");
return hasToken;
}
}
Here is how we use it.
using(JsonReader jr = new MSJsonTextReader(sr))
{
JsonSerializer s = new JsonSerializer();
s.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
s.NullValueHandling = NullValueHandling.Ignore;
s.TypeNameHandling = TypeNameHandling.Auto; // Important!
s.Binder = new MSRestToJsonDotNetSerializationBinder("Server.DataModelsNamespace", "Client.GeneratedModelsNamespace");
T deserialized = s.Deserialize<T>(jr);
return deserialized;
}
Here is our MSRestToJsonDotNetSerializationBinder that completes the compatibility between MS rest and Json.Net.
class MSRestToJsonDotNetSerializationBinder : System.Runtime.Serialization.SerializationBinder
{
public string ServiceNamespace { get; set; }
public string LocalNamespace { get; set; }
public MSRestToJsonDotNetSerializationBinder(string serviceNamespace, string localNamespace)
{
if (serviceNamespace.EndsWith("."))
serviceNamespace = serviceNamespace.Substring(0, -1);
if(localNamespace.EndsWith("."))
localNamespace = localNamespace.Substring(0, -1);
ServiceNamespace = serviceNamespace;
LocalNamespace = localNamespace;
}
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
assemblyName = null;
typeName = string.Format("{0}:#{1}", serializedType.Name, ServiceNamespace); // MS format
}
public override Type BindToType(string assemblyName, string typeName)
{
string jsonDotNetType = string.Format("{0}.{1}", LocalNamespace, typeName.Substring(0, typeName.IndexOf(":#")));
return Type.GetType(jsonDotNetType);
}
}
You could also do it this way:
[JsonConverter(typeof(JsonSubtypes), "ClassName")]
public class Annimal
{
public virtual string ClassName { get; }
public string Color { get; set; }
}
You will need the JsonSubtypes
converter that is not part of Newtonsoft.Json project.
There is another option that allows to serialize custom type property name in Json.NET. The idea is do not write default $type property, but introduce type name as property of class itself.
Suppose we have a Location class:
public class Location
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
First, we need to introduce type property name and modify the class as demonstrated below:
public class Location
{
[JsonProperty("__type")]
public string EntityTypeName
{
get
{
var typeName = string.Format("{0}, {1}", GetType().FullName, GetType().Namespace);
return typeName;
}
}
public double Latitude { get; set; }
public double Longitude { get; set; }
}
Then, set JsonSerializerSettings.TypeNameHandling to TypeNameHandling.None in order the deserializer to skip the rendering of default $type attribute.
That's it.
Example
var point = new Location() { Latitude = 51.5033630, Longitude = -0.1276250 };
var jsonLocation = JsonConvert.SerializeObject(point, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.None, //do not write type property(!)
});
Console.WriteLine(jsonLocation);
Result
{"__type":"Namespace.Location, Namespace","Latitude":51.503363,"Longitude":-0.127625}
Using a custom converter should get the job done.
public CustomConverter : JsonConverter
{
public override bool CanWrite => true;
public override bool CanRead => true;
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 jOjbect = (JObject)JToken.FromObject(value);
jOjbect.Add(new JProperty("type", value.GetType().Name));
jOjbect.WriteTo(writer);
}
}

Handle errors or inconsistencies when parsing JSON using JSON.NET [duplicate]

I'm having a bit of trouble deserializing data returned from Facebook using the JSON.NET libraries.
The JSON returned from just a simple wall post looks like:
{
"attachment":{"description":""},
"permalink":"http://www.facebook.com/permalink.php?story_fbid=123456789"
}
The JSON returned for a photo looks like:
"attachment":{
"media":[
{
"href":"http://www.facebook.com/photo.php?fbid=12345",
"alt":"",
"type":"photo",
"src":"http://photos-b.ak.fbcdn.net/hphotos-ak-ash1/12345_s.jpg",
"photo":{"aid":"1234","pid":"1234","fbid":"1234","owner":"1234","index":"12","width":"720","height":"482"}}
],
Everything works great and I have no problems. I've now come across a simple wall post from a mobile client with the following JSON, and deserialization now fails with this one single post:
"attachment":
{
"media":{},
"name":"",
"caption":"",
"description":"",
"properties":{},
"icon":"http://www.facebook.com/images/icons/mobile_app.gif",
"fb_object_type":""
},
"permalink":"http://www.facebook.com/1234"
Here is the class I am deserializing as:
public class FacebookAttachment
{
public string Name { get; set; }
public string Description { get; set; }
public string Href { get; set; }
public FacebookPostType Fb_Object_Type { get; set; }
public string Fb_Object_Id { get; set; }
[JsonConverter(typeof(FacebookMediaJsonConverter))]
public List<FacebookMedia> { get; set; }
public string Permalink { get; set; }
}
Without using the FacebookMediaJsonConverter, I get an error: Cannot deserialize JSON object into type 'System.Collections.Generic.List`1[FacebookMedia]'.
which makes sense, since in the JSON, Media is not a collection.
I found this post which describes a similar problem, so I've attempted to go down this route: Deserialize JSON, sometimes value is an array, sometimes "" (blank string)
My converter looks like:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
return serializer.Deserialize<List<FacebookMedia>>(reader);
else
return null;
}
Which works fine, except I now get a new exception:
Inside JsonSerializerInternalReader.cs, CreateValueInternal(): Unexpected token while deserializing object: PropertyName
The value of reader.Value is "permalink". I can clearly see in the switch that there's no case for JsonToken.PropertyName.
Is there something I need to do differently in my converter? Thanks for any help.
A very detailed explanation on how to handle this case is available at "Using a Custom JsonConverter to fix bad JSON results".
To summarize, you can extend the default JSON.NET converter doing
Annotate the property with the issue
[JsonConverter(typeof(SingleValueArrayConverter<OrderItem>))]
public List<OrderItem> items;
Extend the converter to return a list of your desired type even for a single object
public class SingleValueArrayConverter<T> : 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)
{
object retVal = new Object();
if (reader.TokenType == JsonToken.StartObject)
{
T instance = (T)serializer.Deserialize(reader, typeof(T));
retVal = new List<T>() { instance };
} else if (reader.TokenType == JsonToken.StartArray) {
retVal = serializer.Deserialize(reader, objectType);
}
return retVal;
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
As mentioned in the article this extension is not completely general but it works if you are fine with getting a list.
The developer of JSON.NET ended up helping on the projects codeplex site. Here is the solution:
The problem was, when it was a JSON object, I wasn't reading past the attribute. Here is the correct code:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize<List<FacebookMedia>>(reader);
}
else
{
FacebookMedia media = serializer.Deserialize<FacebookMedia>(reader);
return new List<FacebookMedia>(new[] {media});
}
}
James was also kind enough to provide unit tests for the above method.
Based on Camilo Martinez's answer above, this is a more modern, type-safe, leaner and complete approach using the generic version of JsonConverter and C# 8.0 as well as implementing the serialization part. It also throws an exception for tokens other than the two expected according to the question. Code should never do more than required otherwise you run the risk of causing a future bug due to mishandling unexpected data.
internal class SingleObjectOrArrayJsonConverter<T> : JsonConverter<ICollection<T>> where T : class, new()
{
public override void WriteJson(JsonWriter writer, ICollection<T> value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.Count == 1 ? (object)value.Single() : value);
}
public override ICollection<T> ReadJson(JsonReader reader, Type objectType, ICollection<T> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return reader.TokenType switch
{
JsonToken.StartObject => new Collection<T> {serializer.Deserialize<T>(reader)},
JsonToken.StartArray => serializer.Deserialize<ICollection<T>>(reader),
_ => throw new ArgumentOutOfRangeException($"Converter does not support JSON token type {reader.TokenType}.")
};
}
}
And then decorate the property thus:
[JsonConverter(typeof(SingleObjectOrArrayJsonConverter<OrderItem>))]
public ICollection<OrderItem> items;
I've changed the property type from List<> to ICollection<> as a JSON POCO typically need only be this weaker type, but if List<> is required, then just replaced ICollection and Collection with List in all the above code.
take a look at the System.Runtime.Serialization namespace in the c# framework, it's going to get you to where you want to be very quickly.
If you want, you can check out some example code in this project (not trying to plug my own work but i just finished pretty much exactly what you are doing but with a different source api.
hope it helps.
.Net Framework
using Newtonsoft.Json;
using System.IO;
public Object SingleObjectOrArrayJson(string strJson)
{
if(String.IsNullOrEmpty(strJson))
{
//Example
strJson= #"{
'CPU': 'Intel',
'PSU': '500W',
'Drives': [
'DVD read/writer'
/*(broken)*/,
'500 gigabyte hard drive',
'200 gigabyte hard drive'
]
}";
}
JsonTextReader reader = new JsonTextReader(new StringReader(strJson));
//Initialize Read
reader.Read();
if (reader.TokenType == JsonToken.StartArray)
{
return JsonConvert.DeserializeObject<List<Object>>(strJson);
}
else
{
Object media = JsonConvert.DeserializeObject<Object>(strJson);
return new List<Object>(new[] {media});
}
}
Note:
"Object" must be defined according to the Json attributes of your response
Expounding upon Martinez and mfanto's answer for Newtonsoft. It does work with Newtonsoft:
Here is an example of doing it with an array instead of a list (and correctly named).
public class SingleValueArrayConverter<T> : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject
|| reader.TokenType == JsonToken.String
|| reader.TokenType == JsonToken.Integer)
{
return new T[] { serializer.Deserialize<T>(reader) };
}
return serializer.Deserialize<T[]>(reader);
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
Then over the attribute write this:
[JsonProperty("INSURANCE")]
[JsonConverter(typeof(SingleValueArrayConverter<InsuranceInfo>))]
public InsuranceInfo[] InsuranceInfo { get; set; }
Newtonsoft will do the rest for you.
return JsonConvert.DeserializeObject<T>(json);
Cheers to Martinez and mfanto!
Believe it or not, this will work with sub items. (It may even have to.) So... inside of my InsuranceInfo, if I have another object/array hybrid, use this again on that property.
This will also allow you to reserialize the object back to json. When it does reserialize, it will always be an array.
I think you should write your class like this...!!!
public class FacebookAttachment
{
[JsonProperty("attachment")]
public Attachment Attachment { get; set; }
[JsonProperty("permalink")]
public string Permalink { get; set; }
}
public class Attachment
{
[JsonProperty("media")]
public Media Media { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("caption")]
public string Caption { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("properties")]
public Properties Properties { get; set; }
[JsonProperty("icon")]
public string Icon { get; set; }
[JsonProperty("fb_object_type")]
public string FbObjectType { get; set; }
}
public class Media
{
}
public class Properties
{
}

Resources