Json.net deserialization array of interfaces - c#

I'm getting a "type is interface, cannot be instanciated" deserialization error with json.net even though I am specifying type on the object I'm trying to deserialize
private static JsonSerializerSettings settings = new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.Auto };
/// <summary>
/// Returns an object created from the jObject and placed in a stub object
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="jObj"></param>
/// <returns></returns>
public static T FromJObject<T>(JToken jObj)
{
if (jObj == null)
{
jObj = new JObject();
}
if (jObj is JValue)
{
return (T)((JValue)jObj).Value;
}
else
{
return (T)JsonConvert.DeserializeObject<T>(jObj.ToString(), settings);
}
}
this is jObj
{
"Id": 2,
"Name": "Name",
"JsonMapEnum": 0,
"Validations": [
{
"Id": 1,
"$type": "JsonMap.Default.JValidation, JsonMap"
}
],
"JSType": 3,
"SubJsonMapEnum": -1,
"$type": "JsonMap.Default.JAttribute, JsonMap"
}
This is the error
Could not create an instance of type JsonMap.Interfaces.IValidation. Type is an interface or abstract class and cannot be instantated. Path 'Validations[0].Id'
It looks like it's trying to turn Id into a Validation object. Why?
these are the interfaces implemented by my types
public interface IJsonMap
{
long Id { get; set; }
String Name { get; set; }
LazyEnum JsonMapEnum { get; set; }
}
public interface IAttribute : IJsonMap
{
IEnumerable<IValidation> Validations { get; set; }
LazyEnum JSType { get; set; }
LazyEnum SubJsonMapEnum { get; set; }
}
public interface IValidation : IJsonMap
{
IEnumerable<IArgument> Arguments { get; set; }
}
this is the call
FromJObject<JAttribute>(CreationJObj)
JAttribute implements IAttribute

Apparently, "$type" has to be the first property in the String literal in order for the type to be caught by the type name handler. I imagine this is because The deserializer isn't checking for the existence of $type, it simply uses the json string reader, and upon finding the first property without the type being set, it fails out.
Here's the method I created to ensure $type is always first
private static JToken ReorderJToken(this JToken jTok)
{
if (jTok is JArray)
{
var jArr = new JArray();
foreach (var token in jTok as JArray)
{
jArr.Add(token.ReorderJToken());
}
return jArr;
}
else if( jTok is JObject)
{
var jObj = new JObject();
foreach(var prop in (jTok as JObject).Properties().OrderBy(x=> x.Name))
{
prop.Value = prop.Value.ReorderJToken();
jObj.Add(prop);
}
return jObj;
}
return jTok;
}

Well clearly it cannot infer the proper type based off the arguments you're feeding it. Without more context of how it's called I can't make what you have work. However, if you call it with an object cast to the interfaces type it will not work. With the definition you have FromObject must always be called with T is the type of a class. First thing to do is make sure you never try to call the method where T is equal to typeof(IOneOfMyInterfaces) because this is guaranteed to fail. If that doesn't fix it I would try calling a different version of the deserialize method;
IMyInterface obj = (IMyInterface)serializer.Deserialize(validatingReader, t);
This works where validatingReader is a JsonReader and t is the Type of the class object implementing IMyInterface. I'm currently using it for generic deserialization with json schemas for validating json correctness.

Related

Converter that uses generics to parse json model

I have a converter class that receives json in input, here are 2 valid examples:
{
"method": "Model",
"payload": {
"key": "value"
}
}
and
{
"method": "OtherModel",
"payload": {
"foo": "bar"
}
}
In C#, I have classes mapped to each possible model:
public class Model
{
public string Key { get; set; }
}
public class OtherModel
{
public string Foo { get; set; }
}
I need a generic converter
How can I use the string value in the method of the JSON to convert in a generic way the content of the payload field?
Is using a huge switch the only way? This is the prototype I have so far but there are hundreds of different models so it will grow quite large...
public IResult ParseJson(string json)
{
Regex regexMessageName = new Regex("\"messageName\": \"(.*?)\"", RegexOptions.Compiled);
var messageName = regexMessageName.Match(json).Groups[1].Value;
switch (messageName)
{
case "Model":
var raw = JsonConvert.DeserializeObject<JsonData<Model>>(json);
return new LogInfoRequestResult<Model> { Raw = raw };
case "OtherModel":
var raw = JsonConvert.DeserializeObject<JsonData<OtherModel>>(json);
return new LogInfoRequestResult<OtherModel> { Raw = raw };
}
}
If you want complete control of your classes, and allow them to evolve independently, then you can have one base class that owns the Method, and then as many subclasses as you want with their own definition of the payload.
First, parse into the baseclass, just to get a strongly typed deserialization of Method
Then, there are a lot of patterns to address branching logic.
If you have 1-2 cases, an if statement is fine
If you have 3-5 cases, you can use a switch
If you have 6-10 cases, you can create a dictionary that maps method name to class type
If you have more than that, you can use the strategy pattern and pass an interface around
Here's an example of how you could write the code:
var json = #"{
'method': 'Model',
'payload': {
'key': 'value'
}
}";
var modelBase = JsonConvert.DeserializeObject<ModelBase>(json);
var methodMapping = new Dictionary<string, Type>()
{
{MethodTypes.Model.ToString(), typeof(Model)},
{MethodTypes.OtherModel.ToString(), typeof(OtherModel)},
};
Type methodClass = methodMapping[modelBase.Method];
var result = JsonConvert.DeserializeObject(json, methodClass);
Note: Since we're programmatically determining the correct type, it's hard to pass to a generic <T>, so this uses the overload of DeserializeObject that takes type as a param
And here are the classes that model incoming messages
public enum MethodTypes
{
Model,
OtherModel
}
public class ModelBase
{
public string Method { get; set; }
}
public class Model : ModelBase
{
public ModelInfo Payload { get; set; }
public class ModelInfo
{
public string Key { get; set; }
}
}
public class OtherModel : ModelBase
{
public ModelInfo Payload { get; set; }
public class ModelInfo
{
public string Foo { get; set; }
}
}
Dictionary<string,string>
If your data is always going to be "foo":"bar" or "key":"value" .... string:string, then Cid's suggesting to use Dictionary<string,string> Payload makes a lot of sense. Then figure out however you want to map from that c# class in a c# constructor that returns whatever type you want.
Additional Resources:
How to handle both a single item and an array for the same property using JSON.net
Deserializing polymorphic json classes without type information using json.net
JSON.NET - Conditional Type Deserialization
Conditionally deserialize JSON string or array property to C# object using JSON.NET?
You can instanciate an object of the expected class using Activator.CreateInstance(), then populate it with JsonConvert.PopulateObject()
In example :
Type t = Type.GetType($"NameSpaceName.{messageName}"); // this must be a fully qualified name
object obj = Activator.CreateInstance(t);
JsonConvert.PopulateObject(json, obj);

Create custom JSON converter using Newtonsoft.JSON library to get additional data without overriding the CanRead and CanWrite flags

I am currently working on a Custom JSON converter to be used in a WebAPI project. The requirement is - I have a DTO object having some properties. The APIs can be consumed by multiple clients. Depending upon a client few of my DTO Entities might have some additional data apart from the properties already present in the DTO Model. I need to create a custom JSON converter to Serialize and Deserialize this data.
//DTO
class AbcDTO
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public List<AdditionalProperty> AdditionalData { get; set; }
}
//AdditionalProperty class
class AdditionalProperty
{
public string Name { get; set; }
public object Value { get; set; }
}
//Request JSON Body
{
"Prop1": "Val1",
"Prop2": "Val2",
"AdditionalProp3": "Val3",
"AdditionalProp4": "Val4"
}
//After Deserialization the object should be as below
AbcDTO dto = {
Prop1 = "Val1",
Prop2 = "Val2",
AdditionalData = [
{ Name = "AdditionalProp3", Value = "Val3" },
{ Name = "AdditionalProp4", Value = "Val4" }]
}
//After Serialization of the above dto object the JSON should convert back to the Request JSON Body format
We don't want to use the JsonExtensionData attribute provided by Newtonsoft.JSON as we would need to keep the property as Dictionary<string, JToken> -- but we don't want to pass JToken to below layers.
Created a custom JSON converter -
class CustomJsonConverter : JsonConverter
{
bool _canWrite = true;
bool _canRead = true;
public override bool CanConvert(Type objectType)
{
return typeof(IEntity).IsAssignableFrom(objectType);
}
public override bool CanWrite
{
get
{
return _canWrite;
}
}
public override bool CanRead
{
get
{
return _canRead;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JObject.Load(reader);
PropertyInfo[] availablePropertyNames = objectType.GetProperties();
List<AdditionalProperties> additionalData = new List<AdditionalProperties>();
IEntity obj;
_canRead = false;
obj = (IEntity)jObject.ToObject(objectType);
_canRead = true;
IEnumerable<JProperty> properties = jObject.Properties();
foreach (JProperty prop in properties)
{
if (availablePropertyNames.Count(x => x.Name.Equals(prop.Name)) == 0)
{
AdditionalProperties addProp = new AdditionalProperties
{
Name = prop.Name,
Value = prop.Value.ToObject<object>(),
};
additionalData.Add(addProp);
}
}
obj.AdditionalData = additionalData;
return obj;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
IEntity obj = (IEntity)value;
List<AdditionalProperties> additionalData = obj.AdditionalData;
JObject jObj;
_canWrite = false;
jObj = (JObject)JToken.FromObject(obj);
_canWrite = true;
jObj.Remove("AdditionalData");
foreach (AdditionalProperties data in additionalData)
{
jObj.Add(data.Name, JToken.FromObject(data.Value));
}
jObj.WriteTo(writer);
}
}
WebAPI ContractResolver creates 1 JSON converter per Entity. Now the issue is _canRead and _canWrite are not thread-safe. Need to use them to use the base implementation provided by Newtonsoft. If we don't use them, the ToObject and FromObject method again calls the custom converter methods internally resulting in infinite recursion. Using them with logs, reduces performance. Is there any way we can create a custom converter using the base implementation of Newtonsoft.JSON serialization/deserialization without using canRead and canWrite flags?
I can also have reference type child properties - say Person contains Address. I want to capture additional data for both Parent and Child entities. The additional data will not contain data of reference type.
It's possible to disable the converter using a thread static variable or ThreadLocal<T> member, as shown in JSON.Net throws StackOverflowException when using JsonConvert or Generic method of modifying JSON before being returned to client. However, I'd like to suggest a simpler way of solving your problem.
You wrote, We dont want to use the JsonExtensionData attribute provided by Newtonsoft.JSON as we need to keep the property as Dictionary and we dont want to pass JToken to below layers. It is not necessary for the extension data dictionary to have values of type JToken. Values of type object are supported for extension data dictionaries, e.g.:
class AbcDTO
{
public AbcDTO() { this.AdditionalData = new Dictionary<string, object>(); }
public string Prop1 { get; set; }
public string Prop2 { get; set; }
[JsonExtensionData]
public Dictionary<string, object> AdditionalData { get; private set; }
}
When the extension data dictionary is of type Dictionary<string, object>, Json.NET will deserialize JSON primitive values to their equivalent .Net primitives -- string, bool, long and so on -- rather than to JValue objects. Only when encountering an additional property whose value is a JSON object or array will a JToken be added to the dictionary, in which case you can use the answers from How do I use JSON.NET to deserialize into nested/recursive Dictionary and List? to convert the JToken to a conventional .Net type. (However, your question states that The additional data will not contain data of reference type, so this should not be necessary.)
Using [JsonExtensionData] in this manner completely avoids the need for a converter while also deserializing primitives as per your requirements, and thus seems much simpler than the original design shown in the question.
Sample .Net fiddle demonstrating that extension properties can be deserialized into AbcDTO and asserting that none of them are of type JToken.

ASP.NET Web API 2 and partial updates

We are using ASP.NET Web API 2 and want to expose ability to partially edit some object in the following fashion:
HTTP PATCH /customers/1
{
"firstName": "John",
"lastName": null
}
... to set firstName to "John" and lastName to null.
HTTP PATCH /customers/1
{
"firstName": "John"
}
... in order just to update firstName to "John" and do not touch lastName at all. Suppose we have a lot of properties that we want to update with such semantic.
This is quite convenient behavior that is exercised by OData for instance.
The problem is that default JSON serializer will just come up with null in both cases, so it's impossible to distinguish.
I'm looking for some way to annotate model with some kind of wrappers (with value and flag set/unset inside) that would allow to see this difference. Any existing solutions for this?
I know that answers which are already given cover all aspects already, but just want to share concise summary of what we ended up doing and what seems to work for us pretty well.
Created a generic data contract
[DataContract]
public class RQFieldPatch<T>
{
[DataMember(Name = "value")]
public T Value { get; set; }
}
Created ad-hoc data cotnracts for patch requests
Sample is below.
[DataContract]
public class PatchSomethingRequest
{
[DataMember(Name = "prop1")]
public RQFieldPatch<EnumTypeHere> Prop1 { get; set; }
[DataMember(Name = "prop2")]
public RQFieldPatch<ComplexTypeContractHere> Prop2 { get; set; }
[DataMember(Name = "prop3")]
public RQFieldPatch<string> Prop3 { get; set; }
[DataMember(Name = "prop4")]
public RQFieldPatch<int> Prop4 { get; set; }
[DataMember(Name = "prop5")]
public RQFieldPatch<int?> Prop5 { get; set; }
}
Business Logic
Simple.
if (request.Prop1 != null)
{
// update code for Prop1, the value is stored in request.Prop1.Value
}
Json format
Simple. Not that extensive as "JSON Patch" standard, but covers all our needs.
{
"prop1": null, // will be skipped
// "prop2": null // skipped props also skipped as they will get default (null) value
"prop3": { "value": "test" } // value update requested
}
Properties
Simple contracts, simple logic
No serialization customization
Support for null values assignment
Covers any types: value, reference, complex custom types, whatever
At first I misunderstood the problem. As I was working with Xml I thought it was quite easy. Just add an attribute to the property and leave the property empty. But as I found out, Json doesn't work like that. Since I was looking for a solution that works for both xml and json, you'll find xml references in this answer. Another thing, I wrote this with a C# client in mind.
The first step is to create two classes for serialization.
public class ChangeType
{
[JsonProperty("#text")]
[XmlText]
public string Text { get; set; }
}
public class GenericChangeType<T> : ChangeType
{
}
I've chosen for a generic and a non-generic class because it is hard to cast to a generic type while this is not important. Also, for xml implementation it is necessary that XmlText is string.
XmlText is the actual value of the property. The advantage is that you can add attributes to this object and the fact that this is an object, not just string. In Xml it looks like: <Firstname>John</Firstname>
For Json this doesn't work. Json doesn't know attributes. So for Json this is just a class with properties. To implement the idea of the xml value (I will get to that later), I've renamed the property to #text. This is just a convention.
As XmlText is string (and we want to serialize to string), this is fine to store the value disregard the type. But in case of serialization, I want to know the actual type.
The drawback is that the viewmodel needs to reference these types, the advantage is that the properties are strongly typed for serialization:
public class CustomerViewModel
{
public GenericChangeType<int> Id { get; set; }
public ChangeType Firstname { get; set; }
public ChangeType Lastname { get; set; }
public ChangeType Reference { get; set; }
}
Suppose I set the values:
var customerViewModel = new CustomerViewModel
{
// Where int needs to be saved as string.
Id = new GenericeChangeType<int> { Text = "12" },
Firstname = new ChangeType { Text = "John" },
Lastname = new ChangeType { },
Reference = null // May also be omitted.
}
In xml this will look like:
<CustomerViewModel>
<Id>12</Id>
<Firstname>John</Firstname>
<Lastname />
</CustomerViewModel>
Which is enough for the server to detect the changes. But with json it will generate the following:
{
"id": { "#text": "12" },
"firstname": { "#text": "John" },
"lastname": { "#text": null }
}
It can work, because in my implementation the receiving viewmodel has the same definition. But since you are talking about serialization only and in case you use another implementation you would want:
{
"id": 12,
"firstname": "John",
"lastname": null
}
That is where we need to add a custom json converter to produce this result. The relevant code is in WriteJson, assuming you would add this converter to the serializer settings only. But for the sake of completeness I've added the readJson code as well.
public class ChangeTypeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// This is important, we can use this converter for ChangeType only
return typeof(ChangeType).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = JToken.Load(reader);
// Types match, it can be deserialized without problems.
if (value.Type == JTokenType.Object)
return JsonConvert.DeserializeObject(value.ToString(), objectType);
// Convert to ChangeType and set the value, if not null:
var t = (ChangeType)Activator.CreateInstance(objectType);
if (value.Type != JTokenType.Null)
t.Text = value.ToString();
return t;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var d = value.GetType();
if (typeof(ChangeType).IsAssignableFrom(d))
{
var changeObject = (ChangeType)value;
// e.g. GenericChangeType<int>
if (value.GetType().IsGenericType)
{
try
{
// type - int
var type = value.GetType().GetGenericArguments()[0];
var c = Convert.ChangeType(changeObject.Text, type);
// write the int value
writer.WriteValue(c);
}
catch
{
// Ignore the exception, just write null.
writer.WriteNull();
}
}
else
{
// ChangeType object. Write the inner string (like xmlText value)
writer.WriteValue(changeObject.Text);
}
// Done writing.
return;
}
// Another object that is derived from ChangeType.
// Do not add the current converter here because this will result in a loop.
var s = new JsonSerializer
{
NullValueHandling = serializer.NullValueHandling,
DefaultValueHandling = serializer.DefaultValueHandling,
ContractResolver = serializer.ContractResolver
};
JToken.FromObject(value, s).WriteTo(writer);
}
}
At first I tried to add the converter to the class: [JsonConverter(ChangeTypeConverter)]. But the problem is that the converter will be used at all times, which creates a reference loop (as also mentioned in the comment in the code above). Also you may want to use this converter for serialization only. That is why I've added it to the serializer only:
var serializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,
Converters = new List<JsonConverter> { new ChangeTypeConverter() },
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
};
var s = JsonConvert.SerializeObject(customerViewModel, serializerSettings);
This will generate the json I was looking for and should be enough to let the server detect the changes.
-- update --
As this answer focusses on serialization, the most important thing is that lastname is part of the serialization string. It then depends on the receiving party how to deserialize the string into an object again.
Serialization and deserialization use different settings. In order to deserialize again you can use:
var deserializerSettings = new JsonSerializerSettings
{
//NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,
Converters = new List<JsonConverter> { new Converters.NoChangeTypeConverter() },
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
};
var obj = JsonConvert.DeserializeObject<CustomerViewModel>(s, deserializerSettings);
If you use the same classes for deserialization then Request.Lastname should be of ChangeType, with Text = null.
I'm not sure why removing the NullValueHandling from the deserialization settings causes problems in your case. But you can overcome this by writing an empty object as value instead of null. In the converter the current ReadJson can already handle this. But in WriteJson there has to be a modification. Instead of writer.WriteValue(changeObject.Text); you need something like:
if (changeObject.Text == null)
JToken.FromObject(new ChangeType(), s).WriteTo(writer);
else
writer.WriteValue(changeObject.Text);
This would result in:
{
"id": 12,
"firstname": "John",
"lastname": {}
}
Here's my quick and inexpensive solution...
public static ObjectType Patch<ObjectType>(ObjectType source, JObject document)
where ObjectType : class
{
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
try
{
String currentEntry = JsonConvert.SerializeObject(source, settings);
JObject currentObj = JObject.Parse(currentEntry);
foreach (KeyValuePair<String, JToken> property in document)
{
currentObj[property.Key] = property.Value;
}
String updatedObj = currentObj.ToString();
return JsonConvert.DeserializeObject<ObjectType>(updatedObj);
}
catch (Exception ex)
{
throw ex;
}
}
When fetching the request body from your PATCH based method, make sure to take the argument as a type such as JObject. JObject during iteration returns a KeyValuePair struct which inherently simplifies the modification process. This allows you to get your request body content without receiving a deserialized result of your desired type.
This is beneficial due to the fact that you don't need any additional verification for nullified properties. If you want your values to be nullified that also works because the Patch<ObjectType>() method only loops through properties given in the partial JSON document.
With the Patch<ObjectType>() method, you only need to pass your source or target instance, and the partial JSON document that will update your object. This method will apply camelCase based contract resolver to prevent incompatible and inaccurate property names from being made. This method will then serialize your passed instance of a certain type and turned into a JObject.
The method then replaces all properties from the new JSON document to the current and serialized document without any unnecessary if statements.
The method stringifies the current document which now is modified, and deserializes the modified JSON document to your desired generic type.
If an exception occur, the method will simply throw it. Yes, it is rather unspecific but you are the programmer, you need to know what to expect...
This can all be done on a single and simple syntax with the following:
Entity entity = AtomicModifier.Patch<Entity>(entity, partialDocument);
This is what the operation would normally look like:
// Partial JSON document (originates from controller).
JObject newData = new { role = 9001 };
// Current entity from EF persistence medium.
User user = await context.Users.FindAsync(id);
// Output:
//
// Username : engineer-186f
// Role : 1
//
Debug.WriteLine($"Username : {0}", user.Username);
Debug.WriteLine($"Role : {0}", user.Role);
// Partially updated entity.
user = AtomicModifier.Patch<User>(user, newData);
// Output:
//
// Username : engineer-186f
// Role : 9001
//
Debug.WriteLine($"Username : {0}", user.Username);
Debug.WriteLine($"Role : {0}", user.Role);
// Setting the new values to the context.
context.Entry(user).State = EntityState.Modified;
This method will work well if you can correctly map your two documents with the camelCase contract resolver.
Enjoy...
Update
I updated the Patch<T>() method with the following code...
public static T PatchObject<T>(T source, JObject document) where T : class
{
Type type = typeof(T);
IDictionary<String, Object> dict =
type
.GetProperties()
.ToDictionary(e => e.Name, e => e.GetValue(source));
string json = document.ToString();
var patchedObject = JsonConvert.DeserializeObject<T>(json);
foreach (KeyValuePair<String, Object> pair in dict)
{
foreach (KeyValuePair<String, JToken> node in document)
{
string propertyName = char.ToUpper(node.Key[0]) +
node.Key.Substring(1);
if (propertyName == pair.Key)
{
PropertyInfo property = type.GetProperty(propertyName);
property.SetValue(source, property.GetValue(patchedObject));
break;
}
}
}
return source;
}
I know I'm a little bit late on this answer, but I think I have a solution that doesn't require having to change serialization and also doesn't include reflection (This article refers you to a JsonPatch library that someone wrote that uses reflection).
Basically create a generic class representing a property that could be patched
public class PatchProperty<T> where T : class
{
public bool Include { get; set; }
public T Value { get; set; }
}
And then create models representing the objects that you want to patch where each of the properties is a PatchProperty
public class CustomerPatchModel
{
public PatchProperty<string> FirstName { get; set; }
public PatchProperty<string> LastName { get; set; }
public PatchProperty<int> IntProperty { get; set; }
}
Then your WebApi method would look like
public void PatchCustomer(CustomerPatchModel customerPatchModel)
{
if (customerPatchModel.FirstName?.Include == true)
{
// update first name
string firstName = customerPatchModel.FirstName.Value;
}
if (customerPatchModel.LastName?.Include == true)
{
// update last name
string lastName = customerPatchModel.LastName.Value;
}
if (customerPatchModel.IntProperty?.Include == true)
{
// update int property
int intProperty = customerPatchModel.IntProperty.Value;
}
}
And you could send a request with some Json that looks like
{
"LastName": { "Include": true, "Value": null },
"OtherProperty": { "Include": true, "Value": 7 }
}
Then we would know to ignore FirstName but still set the other properties to null and 7 respectively.
Note that I haven't tested this and I'm not 100% sure it would work. It would basically rely on .NET's ability to serialize the generic PatchProperty. But since the properties on the model specify the type of the generic T, I would think it would be able to. Also since we have "where T : class" on the PatchProperty declaration, the Value should be nullable. I'd be interested to know if this actually works though. Worst case you could implement a StringPatchProperty, IntPatchProperty, etc. for all your property types.

JSON Array of Objects to Model in WebAPI using FromBody

I am creating a Web Api method that should accept a list of objects via XML or JSON and add them to a database.
Here is a very basic version of what I currently have:
[HttpPost]
public HttpResponseMessage Put([FromBody]ProductAdd productAdd)
{
//do stuff with productadd object
return Request.CreateResponse(HttpStatusCode.OK);
}
The model structure of the list of objects it accepts is as follows:
public class ProductAdd
{
public List<ProductInformation> Products { get; set; }
}
public class ProductInformation
{
public string ProductName { get; set; }
}
The above works perfectly when I am using XML - (Content-Type: application/xml)
<?xml version="1.0" encoding="utf-8"?>
<ProductAdd>
<Products>
<ProductInformation>
<ProductName>Seahorse Necklace</ProductName>
</ProductInformation>
</Products>
<Products>
<ProductInformation>
<ProductName>Ping Pong Necklace</ProductName>
</ProductInformation>
</Products>
</ProductAdd>
But when I attempt to feed the same thing in using JSON (Content-Type: application/json), the Products list is empty
{
"ProductAdd": {
"Products": [
{
"ProductInformation": { "ProductName": "Seahorse Necklace" }
},
{
"ProductInformation": { "ProductName": "Ping Pong Necklace" }
}
]
}
}
Is there an issue with the JSON serializer when there is an array of objects within another object ?
Any ideas on what will fix this ?
Thanks
Edit:
What serializers are you using for XML and Json?
XML: XmlSerializer
JSON: Newtonsoft
The JSON you are sending to your Web API method does not match the structure you are deserializing into. Unlike XML, the root object in JSON does not have a name. You need to remove the wrapper object from your JSON to get it to work:
{
"Products": [
{
"ProductInformation": { "ProductName": "Seahorse Necklace" }
},
{
"ProductInformation": { "ProductName": "Ping Pong Necklace" }
}
]
}
Alternatively, you could change your class structure to ADD a wrapper class, but then you would also need to change your XML to match that.
public class RootObject
{
public ProductAdd ProductAdd { get; set; }
}
In situations where deserialization is mysteriously failing, I find it helpful to serialize a test object and compare the actual output with the desired input. If the output differs from the desired input, that's probably the cause of the bug. In your case, if I deserialize the XML and re-serialize it to JSON, I get:
{
"Products": [
{
"ProductName": "Seahorse Necklace"
},
{
"ProductName": "Ping Pong Necklace"
}
]
}
As you can see, there are two extra levels of indirection in your JSON as compared to the reserialized XML: there is a root object name, which should not be present in JSON, and there are collection element type names, which also should not be present. (Both of these are, however, common features in XML.)
Is it possible to change your JSON so that it doesn't have these extra levels if indirection? (E.g. was this JSON converted from XML via a script for testing purposes, and thus doesn't reflect real requirements?) If so, then your problem is solved.
If not, then here are some options for deserializing it:
To read & write a root object name with you JSON, see the solutions here or here
Or, roll your own proxy wrapper:
public sealed class ProductAddWrapper
{
public ProductAddWrapper()
{
}
public ProductAddWrapper(ProductAdd product)
{
this.ProductAdd = product;
}
public ProductAdd ProductAdd { get; set; }
public static implicit operator ProductAdd(ProductAddWrapper wrapper) { return wrapper.ProductAdd; }
public static implicit operator ProductAddWrapper(ProductAdd product) { return new ProductAddWrapper(product); }
}
Injecting the extra level of indirection into your lists is a bit more difficult. What you will need to do is to restructure the JSON on the fly as you read and write it, adding or removing the extra artificial level of nesting. This can be done with a JsonConverter:
class CollectionWithNamedElementsConverter : JsonConverter
{
static Type GetEnumerableType(Type type)
{
foreach (Type intType in type.GetInterfaces())
{
if (intType.IsGenericType
&& intType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
return intType.GetGenericArguments()[0];
}
}
return null;
}
public override bool CanConvert(Type objectType)
{
return typeof(IEnumerable).IsAssignableFrom(objectType)
&& !typeof(string).IsAssignableFrom(objectType)
&& GetEnumerableType(objectType) != null;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray originalArray = JArray.Load(reader);
if (originalArray == null)
return null;
JArray array = new JArray();
foreach (var item in originalArray)
{
var child = item.Children<JProperty>().FirstOrDefault();
if (child != null)
{
var value = child.Value;
array.Add(child.Value);
}
}
return serializer.Deserialize(new StringReader(array.ToString()), objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var objectType = value.GetType();
var itemType = GetEnumerableType(objectType);
IEnumerable collection = value as IEnumerable;
writer.WriteStartArray();
foreach (object item in collection)
{
writer.WriteStartObject();
writer.WritePropertyName(itemType.Name);
serializer.Serialize(writer, item);
writer.WriteEndObject();
}
writer.WriteEndArray();
}
}
Then use it by applying a [JsonConverter(typeof(CollectionWithNamedElementsConverter))] attribute to collections with item type names:
public class ProductAdd
{
[JsonConverter(typeof(CollectionWithNamedElementsConverter))]
public List<ProductInformation> Products { get; set; }
}

Deserializing an anonymous top-level array in subclasses

I've got a JSON response that is contained in an outer array like this:
[
{
"type": "a"
},
{
"type": "b",
"id": 1
},
{
"type": "c",
"name": "somename"
}
]
I've tried to convert this to object like this:
class LoginOptions
{
public IList<ILoginOption> Options { get; set; }
}
interface ILoginOption
{
[JsonProperty("type")]
LoginType LoginType { get; }
}
class LoginOptionA : ILoginOption{
public LoginType LoginType
{
get { return LoginType.A; }
}
}
class LoginOptionB : ILoginOption{
public LoginType LoginType
{
get { return LoginType.B; }
}
[JsonProperty("id")]
public int Id { get; set; }
}
class LoginOptionC : ILoginOption{
public LoginType LoginType
{
get { return LoginType.C; }
}
[JsonProperty("name")]
public string Name { get; set; }
}
Which results in this exception:
Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'Library.Models.Domain.LoginOptions' 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 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.
I would rather not implement a collection in my LoginOptions class since that would be way too much overhead when I should be able to just store it in the field. Using the [JsonArray] attribute returns a Cannot create and populate list type Library.Models.Domain.LoginOptions.
Most resources I've found deal with a {"name":"value"} pair for the top array, not an anonymous array. How should I deserialize this?
I've changed my code accordingly:
public sealed class LoginOptions
{
[JsonConverter(typeof(LoginOptionConverter))]
public IList<ILoginOption> Options { get; set; }
}
Where my call dispatcher parses the JSON as such:
private List<ILoginOption> DeserializeObject<List<ILoginOption>>(Stream stream)
{
using (StreamReader sr = new StreamReader(stream))
using (JsonReader reader = new JsonTextReader(sr))
{
return new JsonSerializer().Deserialize<List<ILoginOption>>(reader);
}
}
And a custom converter like this:
internal class LoginOptionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ILoginOption);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var item = JObject.Load(reader);
var data = item["type"].Value<string>();
if (data == "UsernamePassword")
{
return item.ToObject<LoginOptionA>();
}
if (data == "Saml")
{
return item.ToObject<LoginOptionB>();
}
if (data == "Crm")
{
return item.ToObject<LoginOptionC>();
}
throw new ArgumentException("Invalid JSON response");
}
}
This throws the error
Could not create an instance of type Library.Models.Domain.ILoginOption. Type is an interface or abstract class and cannot be instantiated.
Using
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects
};
as advised here did not make a difference.
Note that this error is thrown before ever making it inside the custom converter: when the JsonSerializer is created it throws this error.
Instantiation happens here:
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects
};
using (StreamReader sr = new StreamReader(stream))
using (JsonReader reader = new JsonTextReader(sr))
{
return JsonSerializer.Create(settings).Deserialize<List<ILoginOption>>(reader);
}
Since the top level in the JSON is an array, you should deserialize directly to a list. There is no need for a wrapper class to hold the list.
List<ILoginOption> loginOptions =
JsonConvert.DeserializeObject<List<ILoginOption>>(json);
Now, because your list will hold several different types of ILoginObjects, Json.Net will not know which ones to create as it deserializes the list. For that you will need a JsonConverter. This answer shows how you can create a converter to handle this.

Categories