JSON Array of Objects to Model in WebAPI using FromBody - c#

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; }
}

Related

Is there a Newtonsoft equivalent to the [XMLText] property using in xml serialization?

EDIT: I realized it might be imported for context, I'm building this JSON for human readability (I don't see there ever been a business need to parse it back into something meaningful later) which is why I need to keep its formatting as simple as possible
So I have an object I want to serialize to JSON. The serialization is complex, and I've created a helper property to handle that. The helper property successfully returns an object every time that matches that way I want it to be serialized. Unfortunately, the serialization engine still shows the helper functions name, instead of just treating its output as a representation of my objects state.
I'm looking to reach the output below
{
"A" : <The output of the helper property>,
"B" : <The output of the helper property>
}
But I actually get
{
"A" : {
"HelperProperty": <The output of the helper property>
},
"B" : {
"HelperProperty": <The output of the helper property>
}
}
I know in XML formatting there is the [XMLText] attribute that I would apply to the "HelperProperty" property to do this. Is there a similar property in the Newtonsoft landscape? Alternatively, does another Json formatter support this type of operation?
MVCE of what I have now
The object that I am serializing to JSON
[JsonObject(MemberSerialization.OptIn)]
public class SerializationExample
{
[JsonProperty]
public object HelperProperty => (Object)A ?? (Object)B ?? (Object)C ?? "no property specified";
public int? A;
public string B;
public Dictionary<String, int> C;
}
Instantiations of that object and serialization thereof
var a = new SerializationExample() { A = 5 };
var b = new SerializationExample() { B = "five" };
var c = new SerializationExample() { C = new Dictionary<string, int>() { {"number" , 5 } } };
System.IO.File.WriteAllText(#"\users\sidney\desktop\output.json",
JsonConvert.SerializeObject(new Dictionary<string, SerializationExample>() {
{"a",a },{"b",b },{"c",c } }, Formatting.Indented));
How it serializes
{
"a": {
"HelperProperty": 5
},
"b": {
"HelperProperty": "five"
},
"c": {
"HelperProperty": {
"number": 5
}
}
}
How I want it to serialize
{
"a": 5,
"b": "five",
"c": {
"number": 5
}
}
While I couldn't find an exact solution for what I wanted, I was able to find that you were able to control how your json was serialized, courtesy of https://blog.maskalik.com/asp-net/json-net-implement-custom-serialization/.
All I had to do was define a class
public class DirectPropertySerializer : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var name = value as MyClass;
serializer.Serialize(writer, name.HelperProperty);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
And then add an attribute to MyClass
[JsonConverter(typeof(DirectPropertySerializer))]
(Note, as stated above, I don't see a reason to Read this JSON back and try and parse it, so I'm just leaving the ReadJson function as not implemented.

How to validate JSON via schema dynamically in C#

I need to validate JSON response dynamically. Schema should depends on value of specific key in key-pair.
Please, note, that I am using JSON.NET 5.0.8 and cannot upgrade to higher version due to compatibility with infrastructre.
If value of "type" is not "SumProperty" ("CountersProperty" in example), then validate "rule" like string:
"property": [
{
"type": "CountersProperty",
"rule": "MEQUAL"
}
]
But! If value of "type" is "SumProperty", then validate "rule" like array (and "rule" should be inside "config"):
"property": [
{
"type": "SumProperty",
"config": {
"rule": [
{
"type": "MEQUAL",
"value": 2
}
]
}
}
]
So I need some kind of dynamic validation, that can "understand" what kind of property we have and validate it appropriately. JSON response can have multiple of "properties" at the same time, so I can't choose one kind of validation or another, it should work dynamically.
You can do this by implementing a custom JsonConverter.
I made the following classes following your sample input
public class Schema
{
[JsonProperty("Property")]
public List<Property> Properties { get; set; }
}
public abstract class Property
{
public string Type { get; set; }
}
public class NotSumProperty : Property
{
public string Rule { get; set; }
}
public class SumProperty : Property
{
public Config Config { get; set; }
}
public class Config
{
[JsonProperty("Rule")]
public List<Rule> Rules { get; set; }
}
public class Rule
{
public string Type { get; set; }
public int Value { get; set; }
}
Then we define our custom JsonConverter. We override the ReadJson() method to implement our conversion clause, in this case, we evaluate the type of the Property.
public class PropertyConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => typeof(Property).IsAssignableFrom(objectType);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
Property p;
switch ((string)obj["type"])
{
case "SumProperty":
p = new SumProperty();
break;
default:
p = new NotSumProperty();
break;
}
serializer.Populate(obj.CreateReader(), p);
return p;
}
}
Finally, here's the usage:
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects
};
settings.Converters.Add(new PropertyConverter());
Schema schema = JsonConvert.DeserializeObject<Schema>(json, settings);
Another option, if you don't want to write your own converter, is to deserialize into a a dynamic object, and check these values at run time, as demonstrated here.
This could potentially be more useful if you can't define a clear inheritance patter, though it does rely on clients to implement more parsing/validation logic themselves. In other words, it's a little easier to hit unexpected exceptions - it basically moves potential issues from compile-time errors to run-time errors.

C# Newtonsoft JSON - Deserializing Object with collection of unknown objects

I'm struggling with deserialization of the json file using the newtonsoft.json. Object which I want to deserialize looks like this:
public class Device
{
public string Name { get; set; }
public int Id { get; set; }
public string Type { get; set; }
public List<Sensor> Sensors { get; }
public bool IsPaired { get; set; }
}
Sensor class is Virtual.
I have multiple classes which inherit from Sensor class (TemperatureSensor, WaterLevelSensor etc.) and add some new properties. Instances of these classes are stored in Sensors collection.
Json file looks like this:
[
{
"Name":"Device1",
"Id":0,
"Type":"TemperatureSensor",
"Sensors":[
{
"Id":0,
"Type":"TemperatureSensor",
"Data":18.136218099999997,
"ReadIntervalInMiliseconds":5000
},
{
"Id":1,
"Type":"TemperatureSensor",
"Data":18.0999819,
"ReadIntervalInMiliseconds":5000
}
],
"IsPaired":false
},
{
"Name":"Device2",
"Id":1,
"Type":"AutomaticGate",
"Sensors":[
{
"OpenPercentage":0,
"Id":0,
"Type":"AutomaticGate",
"Data":0.0,
"ReadIntervalInMiliseconds":0
}
],
"IsPaired":false
},
{
"Name":"Device3",
"Id":2,
"Type":"Other",
"Sensors":[
{
"IsActive":false,
"Id":0,
"Type":"AirConditioner",
"Data":0.0,
"ReadIntervalInMiliseconds":0
},
{
"Id":1,
"Type":"LightSensor",
"Data":4.0,
"ReadIntervalInMiliseconds":5000
}
],
"IsPaired":false
}
]
I assume that i have to read the "Type" of Sensor from json file, and on this basis create the Object and add it to some collection and then return Device class object with this collection.
I was trying to make custom JsonConverter like in this blog post but with little effect.
You can create a custom JsonConverter to convert Sensor objects to concrete derived classes. Here's a working example of such a JsonConverter:
public class SensorConverter : JsonConverter
{
public override bool CanRead => true;
public override bool CanWrite => false;
public override bool CanConvert(Type objectType)
{
// Don't do IsAssignableFrom tricks here, because you only know how to convert the abstract class Sensor.
return objectType == typeof(Sensor);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
string sensorType = jObject["Type"].Value<string>();
switch (sensorType)
{
case "TemperatureSensor":
return jObject.ToObject<TemperatureSensor>(serializer);
case "AutomaticGate":
return jObject.ToObject<AutomaticGate>(serializer);
case "AirConditioner":
return jObject.ToObject<AirConditioner>(serializer);
case "LightSensor":
return jObject.ToObject<LightSensor>(serializer);
default:
throw new NotSupportedException($"Sensor type '{sensorType}' is not supported.");
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
Then, when deserializing, you will have to add your custom converter to the settings in order for this to work.
Note that your Sensors property is get-only at the moment. You will have to provide a setter in order for NewtonSoft to populate the property.
Another solution that requires much less code is using JsonSubTypes
Assuming an abstract Sensor class, you need to register via custom attribute a known subclass and it's identifier. So in your case, the identifier is property named "Type" and the class mappings is in KnownSubType attributes.
[JsonConverter(typeof(JsonSubtypes), "Type")]
[JsonSubtypes.KnownSubType(typeof(TemperatureSensor), "TemperatureSensor")]
[JsonSubtypes.KnownSubType(typeof(WaterLevelSensor), "WaterLevelSensor")]
[JsonSubtypes.KnownSubType(typeof(AirConditioner), "AirConditioner")]
[JsonSubtypes.KnownSubType(typeof(AutomaticGate), "AutomaticGate")]
[JsonSubtypes.KnownSubType(typeof(LightSensor), "LightSensor")]
public abstract class Sensor
{
}
In your Device class, Sensors property must have a set property.
public List<Sensor> Sensors { get; set;}
Usage:
var items = JsonConvert.DeserializeObject<List<Device>>(json);

Serialize a Json property that is sometimes an array [duplicate]

This question already has answers here:
How to handle both a single item and an array for the same property using JSON.net
(9 answers)
Closed 7 years ago.
Is there any way to serialize a Json object property that varies from decimal to decimal[] in a single operation?
In my Json product feed special offer items are represented as an array (normal price/ sale price). Normal items are just the price. Like so:
[
{
"product" : "umbrella",
"price" : 10.50,
},
"product" : "chainsaw",
"price" : [
39.99,
20.0
]
}
]
The only way I can get it to work is if I make the property an object like so:
public class Product
{
public string product { get; set; }
public object price { get; set; }
}
var productList = JsonConvert.DeserializeObject<List<Product>>(jsonArray);
But if I try to make it decimal[] then it will throw exception on a single decimal value. Making it an object means that the arrays values are a JArray so I have to do some clean up work afterwards and other mapping in my application requires the property type to be accurate so I have to map this to an unmapped property then initialize another property which is no big deal but a little messy with naming.
Is object the only option here or is there some magic I can do with the serializer that either adds single value to array or the second value to a separate property for special offer price?
You have to write a custom converter for that price property (because it's not in well format), and use like this:
public class Product
{
public string product { get; set; }
[JsonConverter(typeof(MyConverter ))]
public decimal[] price { get; set; }
}
public class MyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if(reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize(reader, objectType);
}
return new decimal[] { decimal.Parse(reader.Value.ToString()) };
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And then parse as normal:
var productList = JsonConvert.DeserializeObject<List<Product>>(jsonStr);
Another way to do this is to add a Json field that is dynamic and marked it with JsonProperty attribute so it lines up w/ the actual Json. Then have the real field in .NET that will take that Json field and translate it into a real array like so...
using System;
using System.Linq;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public class Program
{
public static void Main()
{
var json = "{ \"price\": [ 10, 20 ] }";
var json2 = "{ \"price\": 15 }";
var foo1 = JsonConvert.DeserializeObject<Foo>(json);
var foo2 = JsonConvert.DeserializeObject<Foo>(json2);
foo1.Price.ForEach(Console.WriteLine);
foo2.Price.ForEach(Console.WriteLine);
}
}
public class Foo {
[JsonProperty(PropertyName = "price")]
public dynamic priceJson { get; set; }
private List<int> _price;
public List<int> Price {
get {
if (_price == null) _price = new List<int>();
_price.Clear();
if (priceJson is Newtonsoft.Json.Linq.JArray) {
foreach(var price in priceJson) {
_price.Add((int)price);
}
} else {
_price.Add((int)priceJson);
}
return _price;
}
}
}
Live sample at: https://dotnetfiddle.net/ayZBlL

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