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
Related
I've got a simple POCO like this:
public class Edit { ... }
public class CharInsert : Edit
{
public int ParagraphIndex { get; set; }
public int CharacterIndex { get; set; }
public char Character { get; set; }
}
which serializes in JSON like this (note that I'm recording the object type, because of the inheritance):
{
"$type": "MyNamespace.CharInsert, MyAssembly",
"paragraphIndex": 7,
"characterIndex": 15,
"character": "e"
}
But this takes up a HUGE amount of space for a fairly little amount of data. And I have a LOT of them, so I need to be more compact about it.
I made a custom JsonConverter so that it will instead serialize as this:
"CharInsert|7|15|e"
and when I persist a list of these, I get:
[
"CharInsert|7|12|Z",
"CharInsert|7|13|w",
"CharInsert|7|14|i",
"CharInsert|7|15|e",
]
But when I try to deserialize this list, I get the error:
'Error converting value "CharInsert|7|12|Z" to type 'MyNamespace.Edit'
I suppose this is because the actual object is a subclass of the Edit type and it doesn't know which one because it doesn't know how to parse the string. How can I implement this so it can parse the string, resolve the typename contained therein, and then create the needed object type?
An alternative approach to custom converters, consider using [JsonProperty(PropertyName = "")] to shorten the json property names, which should decrease space and you dont have to worry about custom converters.
public class CharInsert : Edit
{
[JsonProperty(PropertyName = "p")]
public int ParagraphIndex { get; set; }
[JsonProperty(PropertyName = "i")]
public int CharacterIndex { get; set; }
[JsonProperty(PropertyName = "c")]
public char Character { get; set; }
}
I figured it out. The issue is that, without type information in the serialized string ("CharInsert|7|15|e"), the deserializer doesn't know what derived class to call.
So I made a JsonConverter for the base Edit type that knows how to parse the string and create and return and object from that string:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//get the persisted value
var s = reader.Value?.ToString();
var fields = s.Split('|');
var typeName = ...get the type name from the field
var type = Type.GetType(typeName);
//create an object from the remaining fields using the ctor(string value) that
//each subclass must have
return System.Activator.CreateInstance(type, new object[] { fields.Skip(1).ToJoinedString("|") });
}
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);
This question already has answers here:
custom serializer for just one property in Json.NET
(3 answers)
Closed 3 years ago.
I am modeling a domain, and for a select few properties, even though their values are simple, I don't want to use a built-in integral (e.g. int) or a very general type (e.g. System.Guid).
So I introduced some simple 'wrapper' types. Combined with implicit operators I am happy with the result:
class Order
{
public Price Price { get; set; }
}
order.Price = 4.95m;
Where Price 'wraps' decimal and stores the value in a private field named value.
I am now serializing objects to JSON, and I would like the object above to be serialized like this:
{
"Price": 4.95
}
But so far I have only been able to serialize it like this, by making the field a public property:
{
"Price": {
"Value": 4.95
}
}
Do you know of a way (preferably with NewtonSoft.Json) to serialize, and deserialize, in the way I described?
You can do this with a custom converter. For example:
public class PriceConverter : JsonConverter<Price>
{
public override Price ReadJson(JsonReader reader, Type objectType, Price existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
var value = Convert.ToDecimal(reader.Value);
return new Price { Value = value };
}
public override void WriteJson(JsonWriter writer, Price value, JsonSerializer serializer)
{
writer.WriteValue(value.Value);
}
}
Now you just need to tell JSON.Net to use the converter. One way is to add it to your serialiser settings:
//Create the settings object and add the converter
var settings = new JsonSerializerSettings();
settings.Converters.Add(new PriceConverter());
//Use the settings
var json = JsonConvert.SerializeObject(order, settings);
var newOrder = JsonConvert.DeserializeObject<Order>(json, settings);
Alternatively, use an attribute to tell JSON.Net which converter to use, either on the property:
public class Order
{
[JsonConverter(typeof(PriceConverter))]
public Price Price { get; set; }
}
Or on the class:
[JsonConverter(typeof(PriceConverter))]
public class Price
{
public decimal Value { get; set; }
}
Using Json.NET 10.0.3. Consider the following sample:
class Foo
{
[JsonProperty("ids")]
public int[] MyIds { get; set; }
}
Obviously, the elements of the array are unnamed. Now consider the following json:
{
"ids": [{
"id": 1
}, {
"id": 2
}
]
}
And then we try to parse it:
var json = #"{""ids"":[{""id"":1},{""id"":2}]}";
var result = JsonConvert.DeserializeObject<Foo>(son);
Parsing the above fails with the following message:
Newtonsoft.Json.JsonReaderException: Unexpected character encountered
while parsing value: {. Path 'ids', line 1, position 9.
I know I can wrap int in a class and name it "id" there, but I'm wondering if this can be done without this extra work. The reason being what appears to be a limitation in SQL Server 2016. See this question.
You can make a custom JsonConverter to translate between the two array formats:
class CustomArrayConverter<T> : JsonConverter
{
string PropertyName { get; set; }
public CustomArrayConverter(string propertyName)
{
PropertyName = propertyName;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray array = new JArray(JArray.Load(reader).Select(jo => jo[PropertyName]));
return array.ToObject(objectType, serializer);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
IEnumerable<T> items = (IEnumerable<T>)value;
JArray array = new JArray(
items.Select(i => new JObject(
new JProperty(PropertyName, JToken.FromObject(i, serializer)))
)
);
array.WriteTo(writer);
}
public override bool CanConvert(Type objectType)
{
// CanConvert is not called when the [JsonConverter] attribute is used
return false;
}
}
To use the converter, mark your array property with a [JsonConverter] attribute as shown below. Note the type parameter for the converter must match the item type of the array, and the second parameter of the attribute must be the property name to use for the values in the JSON array.
class Foo
{
[JsonProperty("ids")]
[JsonConverter(typeof(CustomArrayConverter<int>), "id")]
public int[] MyIds { get; set; }
}
Here is a round-trip demo: https://dotnetfiddle.net/vUQKV1
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.