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);
}
Related
I am trying to deserialize an existing JSON structure to into an object composed of a set of models. The naming in these models are not consistent and I was specifically asked to not change them (renaming, adding attributes, etc).
So, given this Json text (just a small sample):
{
"parameter": {
"alarms": [
{
"id": 1,
"name": "alarm1",
"type": 5,
"min": 0,
"max": 2
}],
"setting-active": true,
"setting-oneRun": true
}
}
would need to be mapped into these models:
public class Alarm
{
public int AlarmId { get; set; }
public string AlarmName { get; set; }
public AlarmType RbcType { get; set; }
public int MinimumTolerated { get; set; }
public int MaximumTolerated { get; set; }
}
public class Setting
{
public bool Active { get; set; }
public bool OneRun { get; set; }
}
public class Parameter
{
public List<Alarm> Alarms { get; set; }
public Setting ParameterSetting { get; set; }
}
So far, im writing a class that extends DefaultContractResolver and overrides maps property names.
MyCustomResolver so far:
public class MyCustomResolver : DefaultContractResolver
{
private Dictionary<string, string>? _propertyMappings;
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
//ModelMappings is a static class that will return a dictionary with mappings per ObjType being deserialized
_propertyMappings = ModelMappings.GetMapping(type);
return base.CreateProperties(type, memberSerialization);
}
protected override string ResolvePropertyName(string propertyName)
{
if (_propertyMappings != null)
{
_propertyMappings.TryGetValue(propertyName, out string? resolvedName);
return resolvedName ?? base.ResolvePropertyName(propertyName);
}
return base.ResolvePropertyName(propertyName);
}
}
Code that Im using to deserialize:
var settings = new JsonSerializerSettings();
settings.DateFormatString = "YYYY-MM-DD";
settings.ContractResolver = new MyCustomResolver();
Parameter p = JsonConvert.DeserializeObject<Parameter>(jsonString, settings);
So I reached a point I need to somehow map the properties in Parameter to values located in the prev json node ("setting-active", "setting-oneRun"). I need to tell the deserializer where these values are.
Can this be done using an extension of DefaultContractResolver ?
I appreciate any tips pointing in the right direction
You can apply ModelMappings.GetMapping(objectType) in DefaultContractResolver.CreateObjectContract():
public class MyCustomResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
var overrides = ModelMappings.GetMapping(objectType);
if (overrides != null)
{
foreach (var property in contract.Properties.Concat(contract.CreatorParameters))
{
if (property.UnderlyingName != null && overrides.TryGetValue(property.UnderlyingName, out var name))
property.PropertyName = name;
}
}
return contract;
}
}
Notes:
By applying the mappings in CreateObjectContract() you can remap both property names and creator parameter names.
Since the contract resolver is designed to resolve contracts for all types, storing a single private Dictionary<string, string>? _propertyMappings; doesn't really make sense.
Unlike your previous question, your current question shows properties from a nested c# object ParameterSetting getting percolated up to the parent object Parameter. Since a custom contract resolver is designed to generate the contract for a single type, it isn't suited to restructuring data between types. Instead, consider using a DTO or converter + DTO in such situations:
public class ParameterConverter : JsonConverter<Parameter>
{
record ParameterDTO(List<Alarm> alarms, [property: JsonProperty("setting-active")] bool? Active, [property: JsonProperty("setting-oneRun")] bool? OneRun);
public override void WriteJson(JsonWriter writer, Parameter? value, JsonSerializer serializer)
{
var dto = new ParameterDTO(value!.Alarms, value.ParameterSetting?.Active, value.ParameterSetting?.OneRun);
serializer.Serialize(writer, dto);
}
public override Parameter? ReadJson(JsonReader reader, Type objectType, Parameter? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var dto = serializer.Deserialize<ParameterDTO>(reader);
if (dto == null)
return null;
existingValue ??= new ();
existingValue.Alarms = dto.alarms;
if (dto.Active != null || dto.OneRun != null)
existingValue.ParameterSetting = new () { Active = dto.Active.GetValueOrDefault(), OneRun = dto.OneRun.GetValueOrDefault() };
return existingValue;
}
}
If your "real" model is too complex to define a DTO, you could create a JsonConverter<Paramater> that (de)serializes the JSON into an intermediate JToken hierarchy, then restructures that. See e.g. this answer to Can I serialize nested properties to my class in one operation with Json.net?.
In some cases, the custom naming of your properties is just camel casing. To camel case property names without the need for explicit overrides, set MyCustomResolver.NamingStrategy to CamelCaseNamingStrategy e.g. as follows:
var settings = new JsonSerializerSettings
{
DateFormatString = "YYYY-MM-DD",
// Use CamelCaseNamingStrategy since many properties in the JSON are just camel-cased.
ContractResolver = new MyCustomResolver { NamingStrategy = new CamelCaseNamingStrategy() },
Converters = { new ParameterConverter() },
};
Demo fiddle here.
I think that the best way to "KEEP IT SIMPLE", you need to define an object that has exactly the properties of the json. Then you can use a library like "Automapper" to define rules of mapping between the "json object" and the "business object".
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;
}
}
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.
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