I am consuming an API that is supposed to return an object, like
{
"some_object": {
"some_field": "some value"
}
}
when that object is null, I would expect
{
"some_object": null
}
or
{
"some_object": {}
}
But what they send me is
{
"some_object": []
}
...even though it's never an array.
When using
JsonSerializer.Deserialize<MyObject>(myJson, myOptions)
an exception is thrown when [] appears where null is expected.
Can I selectively ignore this exception?
My current way of handling this is to read the json and fix it with a regex before deserialization.
I prefer to use System.Text.Json, and not introduce other dependencies, if possible.
This solution uses a custom JsonConverter in System.Text.Json.
If some_object is an array then it will return an empty object (or null if you prefer), and no exception will be thrown. Otherwise it will correctly deserialize the json.
public class EmptyArrayToObjectConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
{
var rootElement = JsonDocument.ParseValue(ref reader);
// if its array return new instance or null
if (reader.TokenType == JsonTokenType.EndArray)
{
// return default(T); // if you want null value instead of new instance
return (T)Activator.CreateInstance(typeof(T));
}
else
{
var text = rootElement.RootElement.GetRawText();
return JsonSerializer.Deserialize<T>(text, options);
}
}
public override bool CanConvert(Type typeToConvert)
{
return true;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
JsonSerializer.Serialize<T>(writer, value, options);
}
}
Decorate your property with the JsonConverter attribute. Your class might look something like this:
public class MyObject
{
[JsonPropertyAttribute("some_object")]
[JsonConverter(typeof(EmptyArrayToObjectConverter<SomeObject>))]
public SomeObject SomeObject { get; set; }
...
}
You can use [OnError] attribute to conditionally suppress exception related with a particular member. Let me try to explain it with an example.
The example class which represents JSON file. It contains a nested class SomeObject.
public class MyObject
{
public int TemperatureCelsius { get; set; }
public SomeObject SomeObject { get; set; }
[OnError]
internal void OnError(StreamingContext context, ErrorContext errorContext)
{
//You can check if exception is for a specific member then ignore it
if(errorContext.Member.ToString().CompareTo("SomeObject") == 0)
{
errorContext.Handled = true;
}
}
}
public class SomeObject
{
public int High { get; set; }
public int Low { get; set; }
}
If sample JSON stream/file contains text as:
{
"TemperatureCelsius": 25,
"SomeObject": []
}
then exception is handled and suppressed as exception is raised for SomeObject member. The SomeObject member is set as null.
If input JSON stream/file contains text as:
{
"TemperatureCelsius": 25,
"SomeObject":
{
"Low": 1,
"High": 1001
}
}
then object is serialized properly with SomeObject representing expected value.
Here is a solution using a custom JsonConverter and Newtonsoft.Json.
This will set SomeObject to null in MyObject if it is an array. You can return a new instance of SomeObject instead by returning (T)Activator.CreateInstance(typeof(T)).
public class ArrayToObjectConverter<T> : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
// this returns null (default(SomeObject) in your case)
// if you want a new instance return (T)Activator.CreateInstance(typeof(T)) instead
return default(T);
}
return token.ToObject<T>();
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanWrite
{
get { return true; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
Note that Newtonsoft.Json ignores CanConvert (since the property is decorated with JsonConverter attribute) it assumes it can write and convert so does not call these methods (you could return false or throw NotImplementedException instead and it will still serialize/deserialize).
In your model, decorate some_object with the JsonConvert attribute. Your class might look something like this:
public class MyObject
{
[JsonProperty("some_object")]
[JsonConverter(typeof(ArrayToObjectConverter<SomeObject>))]
public SomeObject SomeObject { get; set; }
}
I know you said you'd prefer to use System.Text.Json but this might be useful for others using Json.Net.
Update: I did create a JsonConverter solution using System.Text.Json and it is here.
Solutions above work fine, I'll give mine for .NET Core 3 and above, which is just a reader, not a writer (no need). The source json, is buggy, and gives an empty array, when it should be 'null'. So, this custom converter does the correction work.
so: "myproperty":{"lahdidah": 1} is [] when it actually should be: "myproperty": null
Note, the TrySkip, we don't need to eat bogus elements.
public sealed class JsonElementOrArrayFixerConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.StartArray)
{
reader.TrySkip();
return default;
}
return JsonSerializer.Deserialize<T>(ref reader, options);
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
Exception handling is a pet peeve of mine. And I have two articles from other people that I link often on the mater:
https://blogs.msdn.microsoft.com/ericlippert/2008/09/10/vexing-exceptions/
https://www.codeproject.com/Articles/9538/Exception-Handling-Best-Practices-in-NET
I consider them required reading and use them as basis of any discussion on the topic.
As a general rule, Exception should never be ignored. At best they should be caught and published. At worst, they should not even be caught. It is too easy to cause followup issues and make debugging impossible to be careless or overly agressive.
That being said, in this case (deserialisation) some Exceptions could be classified as either a Exogenous or Vexing Exception. Wich are the kind you catch. And with Vexing, you might even swallow them (like TryParse() kinda does).
Usually you want to catch as specific as possible. Sometimes however you got a very wide range of Exceptions with no decent common ancestors, but shared handling. Luckily I once wrote this attempt to replicate TryParse() for someone stuck on 1.1:
//Parse throws ArgumentNull, Format and Overflow Exceptions.
//And they only have Exception as base class in common, but identical handling code (output = 0 and return false).
bool TryParse(string input, out int output){
try{
output = int.Parse(input);
}
catch (Exception ex){
if(ex is ArgumentNullException ||
ex is FormatException ||
ex is OverflowException){
//these are the exceptions I am looking for. I will do my thing.
output = 0;
return false;
}
else{
//Not the exceptions I expect. Best to just let them go on their way.
throw;
}
}
//I am pretty sure the Exception replaces the return value in exception case.
//So this one will only be returned without any Exceptions, expected or unexpected
return true;
}
Related
I have a generic type with 2 constructor each accepting 1 parameter with different types.
There is no problem with serialization. But when I try to deserialize, I get an error that Newtonsoft is Unable to find a constructor to use for this type.
How can I override Newtonsoft's default behavious and choose the constructor to be used for deserialization based on the property types?
I DO NOT have access to the Test class, so I can not change it in any way.
I wish to use another way not write a an entirely custom JsonConverter.
I saw this answer here that you can override the DefaultContractResolver. It works well to choose a different constructor, but I can not see how I can access the properties of the object I am trying to deserialize?
How can I choose a constructor based on the properties types of the object to be deserialized?
public class Test<T, U> where U : struct
{
public Test(T firstProperty)
{
FirstProperty = firstProperty;
}
public Test(U secondProperty)
{
SecondProperty = secondProperty;
}
public T FirstProperty { get; }
public U SecondProperty { get; }
}
There is no way to configure Json.NET to choose a constructor based on the presence or absence of certain properties in the JSON to be deserialized. It simply isn't implemented.
As a workaround, you can create a custom JsonConverter<Test<T, U>> that deserializes to some intermediate DTO that tracks the presence of both properties, and then chooses the correct constructor afterwards. Then you can create a custom contract resolver that applies the converter to all concrete types Test<T, U>.
The following converter and contract resolver perform this task:
class TestConverter<T, U> : JsonConverter where U : struct
{
// Here we make use of the {PropertyName}Specified pattern to track which properties actually got deserialized.
// https://stackoverflow.com/questions/39223335/how-to-force-newtonsoft-json-to-serialize-all-properties-strange-behavior-with/
class TestDTO
{
public T FirstProperty { get; set; }
[JsonIgnore] public bool FirstPropertySpecified { get; set; }
public U SecondProperty { get; set; }
[JsonIgnore] public bool SecondPropertySpecified { get; set; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var dto = serializer.Deserialize<TestDTO>(reader);
if (dto == null)
return null;
else if (dto.FirstPropertySpecified && !dto.SecondPropertySpecified)
return new Test<T, U>(dto.FirstProperty);
else if (!dto.FirstPropertySpecified && dto.SecondPropertySpecified)
return new Test<T, U>(dto.SecondProperty);
else
throw new InvalidOperationException(string.Format("Wrong number of properties specified for {0}", objectType));
}
public override bool CanConvert(Type objectType) => objectType == typeof(Test<T, U>);
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
public class TestContractResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Test<,>))
contract.Converter = (JsonConverter)Activator.CreateInstance(typeof(TestConverter<,>).MakeGenericType(objectType.GetGenericArguments()));
return contract;
}
}
Then use them e.g. as follows:
var json1 = #"{""FirstProperty"":""hello""}";
var json2 = #"{""SecondProperty"": 10101}";
IContractResolver resolver = new TestContractResolver(); // Cache this statically for best performance
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
var test1 = JsonConvert.DeserializeObject<Test<string, long>>(json1, settings);
Assert.AreEqual(test1.FirstProperty, "hello");
var test2 = JsonConvert.DeserializeObject<Test<string, long>>(json2, settings);
Assert.AreEqual(test2.SecondProperty, 10101L);
Notes:
The converter throws an InvalidOperationException if the JSON does not contain exactly one of the two properties.
Feel free to modify this as per your requirements.
The converter does not implement serialization as your Test<T, U> type does not provide a method to track which property was initialized.
The converter does not attempt to handle subclasses of Test<T, U>.
Demo fiddle here.
We have upgraded Newtonsoft.Json from 10.0.3 to 11.0.1 and code that has been working previously does not work anymore.
We have following JSON:
[{"mail-type":"registration","response":"250 OK id=1UjnNr-gdf-C0 ","invoice-id":,"email":"testuser08#test.com"}]
and we call following method:
var events = JsonConvert.DeserializeObject<Event[]>(jsonEvents);
This worked fine on 10.0.3, but does not on 11.0.1. On this version following exception is thrown:
Exception thrown: 'Newtonsoft.Json.JsonReaderException' in
Newtonsoft.Json.dll
Additional information: An undefined token is not a valid
System.Nullable`1[System.Int64]. Path '[0].invoice-id', line 1.
I tried to pass following options to DeserializeObject
var settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore
};
But still the same error. What changes have to be done to make it work on 11.0.1. I am affraid we cannot accomodate JSON output as this comes from third party API.
Your JSON sample is not well-formed. If I upload your JSON to https://jsonlint.com/ then the following error is generated:
Error: Parse error on line 4:
...0 ", "invoice-id": , "email": "testuse
----------------------^
Expecting 'STRING', 'NUMBER', 'NULL', 'TRUE', 'FALSE', '{', '[', got ','
The line in question is as follows:
"invoice-id": ,
According to the JSON standard there needs to be a value between the : and the ,. But, why did this work in Json.NET 10.0? Apparently it was a bug, which was fixed. According to the 11.0.1 release notes:
Fix - Fixed not erroring when reading undefined for nullable long
So if we assume that your Event type looks like this:
public partial class Event
{
[JsonProperty("invoice-id")]
public long? InvoiceId { get; set; }
// Other properties as required
}
Then in 10.0 your JSON could be deserialized successfully using this type, but in 11.0 it cannot. If, however, we change InvoiceId to be an int?:
public partial class Event
{
[JsonProperty("invoice-id")]
public int? InvoiceId { get; set; }
// Other properties as required
}
It fails in both versions. Thus the fix appears to have been to handle int? and long? consistently.
Ideally, you should ask whoever sent you such JSON to fix it so that it is well-formed as defined by http://www.json.org/ and RFC 8259. If you nevertheless need to parse such JSON in the same manner as Json.NET 10.0, you could introduce TolerantNullableLongConverter as follows:
public class TolerantNullableLongConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(long?);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType == JsonToken.Undefined)
return null;
if (reader.Value is long)
return reader.Value;
// string or int or decimal or ...
return serializer.Deserialize<long>(reader);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And apply it to your type as follows:
public partial class Event
{
[JsonProperty("invoice-id")]
[JsonConverter(typeof(TolerantNullableLongConverter))]
public long? InvoiceId { get; set; }
// Other properties as required
}
You can implement workaround with custom converter:
internal class NullableLongFixupConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
writer.WriteValue(value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
// if token undefined - return null
if (reader.TokenType == JsonToken.Undefined)
return null;
// otherwise - value
return (long?) reader.Value;
}
public override bool CanConvert(Type objectType) {
return objectType == typeof(long?);
}
}
Then you can either decorate properties with it:
class Event {
[JsonProperty("invoice-id")]
[JsonConverter(typeof(NullableLongFixupConverter))]
public long? InvoiceId { get; set; }
}
or register globally (will be invoked only for properties of type long?):
JsonConvert.DefaultSettings = () =>
{
var s = new JsonSerializerSettings();
s.Converters.Add(new NullableLongFixupConverter());
return s;
};
I am trying to parse a decimal value from json (eg. id: 4.5) to a poco int and I want an exception.
Background:
This deserialization throws Newtonsoft.Json.JsonSerializationException when encountering a decimal where expecting an int:
httpContent.ReadAsAsync<MyCollection<T>>(
mediaTypeFormatters,
cancellationToken);
MyCollection<T> is a class with a list Result of type T, and T can have an int. Now, I want to catch the ones that throw and keep the rest. So I first extract it as a collection of JObject instead, and then parse them one by one in a try-catch.
var jObjectsMyCollection = await httpContent.ReadAsAsync<MyCollection<Newtonsoft.Json.Linq.JObject>>(
mediaTypeFormatters,
cancellationToken);
foreach (var j in jObjectsMyCollection.Results) {
try {
// now let's parse j one by one
The problem is
I can not make it throw this way, even using the same formatter:
This just deserializes the 4.5 to 4 and does not throw:
var jsonSerializer = JsonSerializer.Create(myMediaTypeFormatters.JsonFormatter.SerializerSettings);
j.ToObject<T>(jsonSerializer)
Same with this:
var ser = myMediaTypeFormatters.JsonFormatter.CreateJsonSerializer();
tObjects.Add(ser.Deserialize<T>(j.CreateReader()));
For the record, the two different formatters used in the different are set up like this:
myMediaTypeFormatters= new MediaTypeFormatterCollection();
myMediaTypeFormatters.JsonFormatter.SerializerSettings.Error += SerializationErrorHander;
myMediaTypeFormatters.JsonFormatter.SerializerSettings.ContractResolver = new SnakeCasePropertyNamesContractResolver();
myMediaTypeFormatters.JsonFormatter.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
IEnumerable<MediaTypeFormatter> mediaTypeFormatters = myMediaTypeFormatters;
The Question:
How can I make it throw at exactly the same data as ReadAsAsync does?
Am I doing soemthing wrong in reusing the MediaTypeFormatters?
Json.NET does seem to have inconsistencies in how it converts floating-point JSON values to integers. E.g., using 10.0.2:
JsonConvert.DeserializeObject<int>("4.5") fails.
JToken.Parse("4.5").ToObject<int>() succeeds and returns 4.
JsonConvert.DeserializeObject<uint>("4.5") succeeds and returns 4.
JsonConvert.DeserializeObject<long>("4.5") succeeds and returns 4.
(In fact, directly deserializing "4.5" to an int seems to be the only case that fails. Json.NET will deserialize "4.5" directly or indirectly to any other integral type.)
The difference seems to arise from inconsistencies between JsonTextReader.ParseReadNumber() (which is called when deserializing JSON directly) and JsonReader.ReadAsInt32() (which is called when deserializing from a JToken). The former checks that the textual JSON value is really an integer when deserializing to an int while the latter simply calls Convert.ToInt32() which happily returns a rounded value.
If you want, you could report an issue about the inconsistency.
In the meantime, you have a couple of options to avoid the inconsistency. Firstly, you could introduce a custom JsonConverter for integers that throws an exception when trying to deserialize a floating point value to an integer, then use that when deserializing from a JToken hierarchy:
public class StrictIntConverter : StrictIntConverterBase
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(int) || objectType == typeof(int?);
}
}
public abstract class StrictIntConverterBase : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
Type type = (Nullable.GetUnderlyingType(objectType) ?? objectType);
bool isNullable = (Nullable.GetUnderlyingType(objectType) != null);
if (reader.TokenType == JsonToken.Null)
{
if (isNullable)
return null;
throw new JsonSerializationException(string.Format("Null value for {0}", objectType));
}
else if (reader.TokenType == JsonToken.Float)
{
throw new JsonSerializationException(string.Format("Floating-point value {0} found for {1}.", reader.Value, type));
}
else
{
// Integer or string containing an integer
if (reader.Value.GetType() == type)
return reader.Value;
return JToken.Load(reader).ToObject(type);
}
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And then do:
var jsonSerializer = JsonSerializer.Create(myMediaTypeFormatters.JsonFormatter.SerializerSettings;
jsonSerializer.Converters.Add(new StrictIntConverter());
j.ToObject<T>(jsonSerializer)
Sample fiddle #1.
Another option would be to use Json.NET's serialization error handling to catch and swallow exceptions when deserializing collection items inside your MyCollection<T> type, for instance:
public class MyCollection<T> : Collection<T>
{
[OnError]
void OnError(StreamingContext context, ErrorContext errorContext)
{
if (errorContext.OriginalObject != this)
{
// Error occurred deserializing an item in the collection. Swallow it.
errorContext.Handled = true;
}
}
}
This allows you to deserialize directly to your MyCollection<T> and skip the intermediate JToken representation. Sample fiddle #2.
When serializing arbitrary data via JSON.NET, any property that is null is written to the JSON as
"propertyName" : null
This is correct, of course.
However I have a requirement to automatically translate all nulls into the default empty value, e.g. null strings should become String.Empty, null int?s should become 0, null bool?s should be false, and so on.
NullValueHandling is not helpful, since I dont want to Ignore nulls, but neither do I want to Include them (Hmm, new feature?).
So I turned to implementing a custom JsonConverter.
While the implementation itself was a breeze, unfortunately this still didnt work - CanConvert() is never called for a property that has a null value, and therefore WriteJson() is not called either. Apparently nulls are automatically serialized directly into null, without the custom pipeline.
For example, here is a sample of a custom converter for null strings:
public class StringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(string).IsAssignableFrom(objectType);
}
...
public override void WriteJson(JsonWriter writer,
object value,
JsonSerializer serializer)
{
string strValue = value as string;
if (strValue == null)
{
writer.WriteValue(String.Empty);
}
else
{
writer.WriteValue(strValue);
}
}
}
Stepping through this in the debugger, I noted that neither of these methods are called for properties that have a null value.
Delving into JSON.NET's sourcecode, I found that (apparently, I didnt go into a lot of depth) there is a special case checking for nulls, and explictly calling .WriteNull().
For what it's worth, I did try implementing a custom JsonTextWriter and overriding the default .WriteNull() implementation...
public class NullJsonWriter : JsonTextWriter
{
...
public override void WriteNull()
{
this.WriteValue(String.Empty);
}
}
However, this can't work well, since the WriteNull() method knows nothing about the underlying datatype. So sure, I can output "" for any null, but that doesnt work well for e.g. int, bool, etc.
So, my question - short of converting the entire data structure manually, is there any solution or workaround for this?
Okay, I think I've come up with a solution (my first solution wasn't right at all, but then again I was on the train). You need to create a special contract resolver and a custom ValueProvider for Nullable types. Consider this:
public class NullableValueProvider : IValueProvider
{
private readonly object _defaultValue;
private readonly IValueProvider _underlyingValueProvider;
public NullableValueProvider(MemberInfo memberInfo, Type underlyingType)
{
_underlyingValueProvider = new DynamicValueProvider(memberInfo);
_defaultValue = Activator.CreateInstance(underlyingType);
}
public void SetValue(object target, object value)
{
_underlyingValueProvider.SetValue(target, value);
}
public object GetValue(object target)
{
return _underlyingValueProvider.GetValue(target) ?? _defaultValue;
}
}
public class SpecialContractResolver : DefaultContractResolver
{
protected override IValueProvider CreateMemberValueProvider(MemberInfo member)
{
if(member.MemberType == MemberTypes.Property)
{
var pi = (PropertyInfo) member;
if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition() == typeof (Nullable<>))
{
return new NullableValueProvider(member, pi.PropertyType.GetGenericArguments().First());
}
}
else if(member.MemberType == MemberTypes.Field)
{
var fi = (FieldInfo) member;
if(fi.FieldType.IsGenericType && fi.FieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
return new NullableValueProvider(member, fi.FieldType.GetGenericArguments().First());
}
return base.CreateMemberValueProvider(member);
}
}
Then I tested it using:
class Foo
{
public int? Int { get; set; }
public bool? Boolean { get; set; }
public int? IntField;
}
And the following case:
[TestFixture]
public class Tests
{
[Test]
public void Test()
{
var foo = new Foo();
var settings = new JsonSerializerSettings { ContractResolver = new SpecialContractResolver() };
Assert.AreEqual(
JsonConvert.SerializeObject(foo, Formatting.None, settings),
"{\"IntField\":0,\"Int\":0,\"Boolean\":false}");
}
}
Hopefully this helps a bit...
Edit – Better identification of the a Nullable<> type
Edit – Added support for fields as well as properties, also piggy-backing on top of the normal DynamicValueProvider to do most of the work, with updated test
I'm deserializing some properties to a Dictionary<string, object>.
When I deserialize some json, it populates the Dictionary with Int64 objects rather than Int32. I would like it to choose Int32 as the default well knowing that I could have javascript Numerics that would overflow on conversion. Throwing an exception in that case would be entirely acceptable.
Is there any way to achieve that? I'm hoping for some nice attributes or a convenient interface that could be implemented and added to the JsonSerializer. And I fear that I have to go deep down into the depths of Json.NET.
Basically I would like to have some way to control the known types for the objects so that I could get Int32's instead of Int64 and DateTimes instead of Strings.
As far as I know, there is no built-in way to do that.
There was an issue on this subject, but it has been closed.
Some comments from the author on the issue:
Json.NET by default reads integer values as Int64 because there is no way to know whether the value should be Int32 or Int64, and Int64 is less likely to overflow. For a typed property the deserializer knows to convert the Int64 to a Int32 but because your property is untyped you are getting an Int64. [...] It is just the way Json.NET has to work.
The easiest solution would of coure be to change the type to Dictionary<string, int>, but I suppose you are not only reading numerics and thus are stuck with object.
Another option would be to either use Serialization Callbacks and manually convert those Int64s to Int32 or create your own Contract Resolver JsonConverter and directly control the (de-)serialization.
Edit: I created a little example to be more specific.
Here is a very basic converter that only works with your specifc Dictionary:
public class Int32Converter : JsonConverter {
public override bool CanConvert(Type objectType) {
// may want to be less concrete here
return objectType == typeof(Dictionary<string, object>);
}
public override bool CanWrite {
// we only want to read (de-serialize)
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
// again, very concrete
Dictionary<string, object> result = new Dictionary<string, object>();
reader.Read();
while (reader.TokenType == JsonToken.PropertyName) {
string propertyName = reader.Value as string;
reader.Read();
object value;
if (reader.TokenType == JsonToken.Integer)
value = Convert.ToInt32(reader.Value); // convert to Int32 instead of Int64
else
value = serializer.Deserialize(reader); // let the serializer handle all other cases
result.Add(propertyName, value);
reader.Read();
}
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
// since CanWrite returns false, we don't need to implement this
throw new NotImplementedException();
}
}
You can either use attributes to decorate members with your converter or pass it as parameter to a (de-)serialize method. Here's an example where I used an attribute:
[JsonObject]
public class MyObject {
[JsonConverter(typeof(Int32Converter))]
public Dictionary<string, object> Properties { get; set; }
}
And here's the code I used to test the implementation:
class Program {
static void Main(string[] args) {
MyObject test = new MyObject();
test.Properties = new Dictionary<string, object>() { { "int", 15 }, { "string", "hi" }, { "number", 7 } };
Print("Original:", test);
string json = JsonConvert.SerializeObject(test);
Console.WriteLine("JSON:\n{0}\n", json);
MyObject parsed = JsonConvert.DeserializeObject<MyObject>(json);
Print("Deserialized:", parsed);
}
private static void Print(string heading, MyObject obj) {
Console.WriteLine(heading);
foreach (var kvp in obj.Properties)
Console.WriteLine("{0} = {1} of {2}", kvp.Key, kvp.Value, kvp.Value.GetType().Name);
Console.WriteLine();
}
}
Without the converter, the result would be:
Deserialized:
int = 15 of Int64
string = hi of String
number = 7 of Int64
and with the converter it is:
Deserialized:
int = 15 of Int32
string = hi of String
number = 7 of Int32
Try
var variable = Convert.ToInt32(object)
Iterate the Dictionary<string,object> once and rewrite its object with this Int32, or do the Int32 conversion each time you read the object.
This is working well for me:
public class ParseNumbersAsInt32Converter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(long) || objectType == typeof(long?) || objectType == typeof(object);
}
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.Value != null && reader.Value is long)
{
return Convert.ToInt32(reader.Value);
}
return reader.Value;
}
}
I'm accepting Enzi's answer since it's what I was asking for.
However, since then I've changed my strategy.
Right now I'm deserializing to a ChangeSet<T> which instead of the dictionary has a strongly typed Entity (T) object with the changes. It also has a List<string> with the property names of the properties that was present in the incoming json. I then populate that list during deserialization using a custom MediaFormatter. That way I get a strongly typed object and correct deserialization of all properties, and I know from the list what properties I should set on my collection of T when I want to do my batch operation.
This way I basically use my entities as DTO's without having to have a myriad of different DTO's for different batch operations. Is pretty sleek if I do say so myself. :)
I found this question, because I hit the same problem. I decided to just go back and convert them.
var items = GetDataFile<Item[]>("Items");
foreach(var m in items)
{
// Deserialized int becomes long if object type. I want int.
foreach(var pk in m.Properties.Keys)
{
if(m.Properties[pk] is long)
{
m.Properties[pk] = Convert.ToInt32(m.Properties[pk]);
}
}
}