We are being supplied some JSON from another system that has decided it would be fun to present integers in scientific notation. As it stands JSON.NET throws an exception of:
Input string 9.658055e+06 is not a valid integer.
I have been able to recreate the issue using some simple code:
public class TestClass
{
public int Value { get; set; }
}
static void Main(string[] args)
{
var json = "{Value:9.658055e+06}";
var xx = JsonConvert.DeserializeObject<TestClass>(json);
}
Any ideas how i can get the library to deserialize this correctly?
UPDATE: Thanks for all answers, for the record changing the type to Int64 or double would not be possible for other reasons, but the converter class has done the job
Two ways you can do this:
First and easiest is to change the type of your Value property to System.Int64. This would most likely "just work".
If, however, it really needs to be an int, you could do something like this:
public class TestClass
{
[JsonConverter(typeof(JsonScientificConverter))]
public int Value { get; set; }
}
static void Main(string[] args)
{
var json = "{Value:9.658055e+06}";
var xx = JsonConvert.DeserializeObject<TestClass>(json);
}
public class JsonScientificConverter : JsonConverter
{
public override bool CanRead { get { return true; } }
public override bool CanConvert(Type objectType)
{
return true;
}
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)
{
return (int)(serializer.Deserialize<System.Int64>(reader));
}
}
Here you're reading it as a System.Int64, but then casting it to a regular int. So you have an int, but you have to change the class and write a custom converter, which might be overcomplicating things.
you'll need to deserialize the string to Int64 equivalent -
var parsed = JsonConvert.DeserializeObject<Int64>("9.658055e+06"); //9658055
You can serialize the value to double, and access the int equivalent with another property that is hidden to the serialization (to keep the json the same):
public class TestClass
{
public double Value
{
get { return (double)IntValue; }
set { _IntValue = (int)value; }
}
private int _IntValue;
[JsonIgnore]
public int IntValue {
get { return _IntValue; }
set { _IntValue = value; }
}
}
static void Main(string[] args)
{
var json = "{Value:9.658055e+06}";
var xx = JsonConvert.DeserializeObject<TestClass>(json);
Console.WriteLine(JsonConvert.SerializeObject(xx));
Console.WriteLine(xx.IntValue);
Console.ReadKey();
}
As you see, the value gets correctly parsed, and when you reserialize the object, the Json stays the same, thanks to the [JsonIgnore] attribute on the IntValue property.
Related
I had a class that I was regularly serializing and deserializing.
One of its fields used to be a string to represent a road, but has since been changed to its own class.
class Foo
{
public string RoadRef;
}
Has now been changed to:
class Foo
{
public Road RoadRef;
}
class Road
{
public Road(string val)
{
Lane = val[0];
Number = int.Parse(val.Substring(1));
}
public char Lane;
public int Number = 1;
}
Now I'm getting errors when I try to deserialize from strings that were serialized with the old version of the class. I have a lot of old serialized files that I don't want to go back and reserialize to the new format, especially since changes like this will likely happen again.
I should be able to specify a custom method to Deserialize (and Serialize) for JsonConvert.Deserialize right?
Is there a better approach to deal with this problem?
The closest I've got so far is adding a [JsonConstructor] attribute above the constructor as suggested here, but it didn't help the deserializing crashes.
The original JSON looks like this: {"RoadRef":"L1"}
It throws this exception:
Newtonsoft.Json.JsonSerializationException: 'Error converting value
"L1" to type 'Road'. Path 'RoadRef', line 1, position 15.'
ArgumentException: Could not cast or convert from System.String to Vis_Manager.Road.
As stuartd suggest, you could use a custom JsonConverter to explicitly say how to convert a string to your data type.
void Main()
{
string json = #"{""RoadRef"":""L1""}";
var data = JsonConvert.DeserializeObject<Foo>(json);
}
public class RoadConverter : JsonConverter<Road>
{
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, Road value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override Road ReadJson(JsonReader reader, Type objectType, Road existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.ValueType != typeof(string))
return (Road)serializer.Deserialize(reader, objectType);
var data = reader.Value as string;
return new Road(data);
}
}
public class Foo
{
[JsonConverter(typeof(RoadConverter))]
public Road RoadRef { get; set; }
}
public class Road
{
public Road(string val)
{
Lane = val[0];
Number = int.Parse(val.Substring(1));
}
public char Lane { get; set; }
public int Number { get; set; } = 1;
}
If you don't want to use Attribute decorator and keep your model out of any serialization concern, you can use it as parameter of DeserialzeObject method
var data = JsonConvert.DeserializeObject<Foo>(json, new RoadConverter());
Rather than using a custom JsonConverter you could use the implicit operator to convert directly from a string:
public class Road
{
public Road(string val)
{
Lane = val[0];
Number = int.Parse(val.Substring(1));
}
public char Lane { get; set; }
public int Number { get; set; } = 1;
public static implicit operator Road(string val)
{
return new Road(val);
}
}
My solution was to add an overriding cast from string to the class.
The JSON Deserialize method tries to explicitly cast the string value to the class, which is now possible.
public Road(string val)
{
Lane = val[0];
Number = int.Parse(val.Substring(1));
}
public static explicit operator Road(string val)
{
return new Road(val);
}
I am using JsonConvert.SerializeObject to serialize a model object.
The server expects all fields as strings. My model object has numeric properties and string properties. I can not add attributes to the model object. Is there a way to serialize all property values as if they were strings? I have to support only serialization, not deserialization.
You can provide your own JsonConverter even for numeric types. I've just tried this and it works - it's quick and dirty, and you almost certainly want to extend it to support other numeric types (long, float, double, decimal etc) but it should get you going:
using System;
using System.Globalization;
using Newtonsoft.Json;
public class Model
{
public int Count { get; set; }
public string Text { get; set; }
}
internal sealed class FormatNumbersAsTextConverter : JsonConverter
{
public override bool CanRead => false;
public override bool CanWrite => true;
public override bool CanConvert(Type type) => type == typeof(int);
public override void WriteJson(
JsonWriter writer, object value, JsonSerializer serializer)
{
int number = (int) value;
writer.WriteValue(number.ToString(CultureInfo.InvariantCulture));
}
public override object ReadJson(
JsonReader reader, Type type, object existingValue, JsonSerializer serializer)
{
throw new NotSupportedException();
}
}
class Program
{
static void Main(string[] args)
{
var model = new Model { Count = 10, Text = "hello" };
var settings = new JsonSerializerSettings
{
Converters = { new FormatNumbersAsTextConverter() }
};
Console.WriteLine(JsonConvert.SerializeObject(model, settings));
}
}
This is basically a follow-on to the question Newtonsoft Object → Get JSON string .
I have an object that looks like this:
[JsonConverter(typeof(MessageConverter))]
public class Message
{
public Message(string original)
{
this.Original = original;
}
public string Type { get; set; }
public string Original { get; set; }
}
My requirement is to store the original JSON string as part of the object when I initialise it. I have been able to (partially) successfully achieve this using a custom JsonConverter and then basically doing something like this in the JsonConverter:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
return null;
JObject obj = JObject.Load(reader);
return new Message(obj.ToString(Formatting.None))
{
Type = obj["type"].ToString()
};
}
However the problem I run into is when I try to inherit from Message with something like
public class CustomMessage : Message
{
public string Prop1 { get; set; }
}
For obvious reasons my code fails because it tries to return a new Message() not a new CustomMessage().
So short of implementing a big if statement with all my sub-types and manually binding using something like JObject["prop"].ToObject<T>() how can I successfully populate the Original property while still binding all my sub-type values using the default deserialisation?
NOTE: The reason this is being done is because the original message might contain data that isn't actually being bound so I can't just add a property which serialises the object as it stands.
The following solution works
One thing you can do is to decorate each child class by generic JsonConverter attribute.
[JsonConverter(typeof(MessageConverter<Message>))]
public class Message
{
public Message(string original)
{
this.Original = original;
}
public string Type { get; set; }
public string Original { get; set; }
}
[JsonConverter(typeof(MessageConverter<CustomMessage>))]
public class CustomMessage : Message
{
public CustomMessage(string original) : base(original)
{
}
public string Prop1 { get; set; }
}
public class MessageConverter<T> : JsonConverter where T : Message
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
return null;
JObject obj = JObject.Load(reader);
var customObject = JsonConvert.DeserializeObject<T>(obj.ToString(), new JsonSerializerSettings
{
ContractResolver = new CustomContractResolver()
});
customObject.Original = obj.ToString();
return customObject;
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
//This will remove our declared Converter
public class CustomContractResolver : DefaultContractResolver
{
protected override JsonConverter ResolveContractConverter (Type objectType)
{
return null;
}
}
And then you can use the same serializer for all of the child classes
CustomMessage x = JsonConvert.DeserializeObject<CustomMessage>("{\"type\":\"Test\",\"Prop1\":\"Prop1\"}");
Message y = JsonConvert.DeserializeObject<Message>("{\"type\":\"Test\"}");
Here is the output screen shot
I'm leaving the question open in the hope that someone comes up with a better answer but I've temporarily used the following solution to resolve my problem.
public static class MessageExtensions
{
public static T Deserialize<T>(this string message) where T : Message
{
T instance = Activator.CreateInstance<T>();
instance.Original = message;
JsonConvert.PopulateObject(message, instance);
return instance;
}
}
I use the exist API and can't change it.
So I have some variable - CellProviderID.
It looks like it is an int, because when I set the int value the server returns the expected response.
"CellProviderID":5,"CellProviderID2":7,"CellProviderID3":2
The problem appears when I leave this value empty and after serialization I get:
"CellProviderID":0,"CellProviderID2":0,"CellProviderID3":0
because 0 is default value for int type. And the server returns an error:
{"ErrorCode":10,"ErrorMessage":"Cell provider was specified, but cell number is blank, Pager provider was specified, but pager number is blank"}
So it looks rather like it's some enum, and 0 hasn't a value.
It works well when I change serialized json from
"CellProviderID":0,"CellProviderID2":0,"CellProviderID3":0
to
"CellProviderID":"","CellProviderID2":"","CellProviderID3":""
How should I initialize the properties to be able to setup int values and get "" when I leave this property empty?
[JsonProperty]
public int CellProviderID { get; set; }
[JsonProperty]
public int CellProviderID2 { get; set; }
[JsonProperty]
public int CellProviderID3 { get; set; }
you can change return type of your properties from int to int?, so you will get null value as default.
It will be serialized as
"CellProviderID":null,"CellProviderID2":null,"CellProviderID3":null
so you can have a deal with that.
Next, you must change your serialization to ignore null values. If you're using Json.Net, you serialization must look like:
JsonConvert.SerializeObject(movie,Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })
So, your server will not receive non-initialized values
You could optionally write a Custom JsonConverter and handle the custom serialization/deserialization yourself.
internal class Inherited
{
public int MyProperty { get; set; }
public int MyProperty2 { get; set; }
}
internal class CustomConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Inherited);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var data = value as Inherited;
if (data != null)
{
writer.WriteStartObject();
foreach (var item in typeof(Inherited).GetProperties())
{
writer.WritePropertyName(item.Name);
switch (item.PropertyType.Name)
{
case "Int32":
{
writer.WriteValue(default(int) == (int)item.GetValue(data, null) ? "" : item.GetValue(data, null).ToString());
break;
}
}
}
writer.WriteEnd();
}
}
}
JsonSerializerSettings obj = new JsonSerializerSettings();
obj.Converters.Add(new CustomConverter());
var result = JsonConvert.SerializeObject(new Inherited(0) { MyProperty = 0, MyProperty2 = 0 }, obj);
Results in -
{"MyProperty":"","MyProperty2":""}
I have a class that has a collection which is readonly and has no setter.
Is there a way to de-serialize some json and get it call a method on the instance instead of the property setter?
For example:
public class User
{
private ObservableCollection<Movie> _movies;
public string Name { get; set; }
public ReadOnlyCollection<Movie> FavouriteMovies { get; set; }
public void AddMovie(Movie movie) { .. }
//-or-
public void AddMovies(IEnumerable<Movie> movies){ .. }
}
The only way to get things into the _movies backing field is via the method AddMovies. So when trying to deserialize some valid json which has an array of Movies in the json, it will call AddMovie or AddMovies...
Ninja update by PK:
I've forked the fiddle below using a collection of classes instead of simple strings, for a more complex example that now works, based on the answer below.
Use a JsonConverter to do custom conversion of your json. Here's a simple example of how that might work. Given a class like this:
public class MyClass
{
private List<string> backingField;
public string Name { get; set; }
public IReadOnlyCollection<string> MyStrings { get; private set; }
public MyClass()
{
backingField = new List<string>();
MyStrings = backingField.AsReadOnly();
}
public void AddString(string item)
{
backingField.Add(item);
}
}
And JSON like this:
{
"MyStrings": [
"Foo",
"Bar"
],
"Name":"My stuff"
}
You could create a converter to read that json and call AddString to populate the backingField like this:
public class MyClassConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var obj = new MyClass();
var jObj = JObject.Load(reader);
JsonConvert.PopulateObject(jObj.ToString(), obj); // populate fields we don't need any special handling for
var stringsProp = jObj["MyStrings"];
if (stringsProp != null)
{
var strings = stringsProp.ToObject<List<string>>();
foreach (var s in strings)
{
obj.AddString(s);
}
}
return obj;
}
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(MyClass);
}
}
Now to use it, and keep MyClass ignorant of how it get deserialized, you can simply do something like this:
var m = JsonConvert.DeserializeObject(json, typeof(MyClass), new MyClassConverter()) as MyClass;
Here's a fiddle