The following is a model based on the response from an API (using sample data):
public class SchoolInfoModel
{
public string Name { get; set; }
public string Website { get; set; }
public Address Address { get; set; }
public List<SchoolTypeModel> SchoolTypes { get; set; }
}
The SchoolTypeModel is where I'm stuck. The SchoolType will return a list but the content of the list might contain one other model or two other models or more. Basically a list of a number of different models. But I do not know in advance which models I receive. How do I map these?
Examples of "SchoolType" models I can receive:
public class HighSchoolModel
{
public string Type { get; set; }
public string SubName { get; set; }
public bool BA { get; set; }
public bool CP { get; set; }
public bool HU { get; set; }
public bool MN { get; set; }
public bool TI { get; set; }
}
public class SpecialPurposeSchoolModel
{
public string Type { get; set; }
public string SubName { get; set; }
public bool AH { get; set; }
}
I have a total of about 10 different types of school.
Here's a way you can achieve this with JSON.Net (which means it will work in .NET Framework 4.8). You can create a custom converter to figure out what properties exist in this JSON and use that to determine which type to deserialise to. For example:
public class SchoolTypeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
=> objectType == typeof(SchoolTypeModel);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
if (obj.ContainsKey("AH"))
{
return obj.ToObject<SpecialPurposeSchoolModel>(serializer);
}
if (obj.ContainsKey("BA"))
{
return obj.ToObject<HighSchoolModel>(serializer);
}
// We have no idea what this is, so panic
throw new Exception("No idea what to do with this value!");
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
// We are only using this to read JSON
throw new NotImplementedException();
}
}
And to read the JSON:
var settings = new JsonSerializerSettings();
settings.Converters.Add(new SchoolTypeConverter());
var result = JsonConvert.DeserializeObject<SchoolInfoModel>(yourJsonString,
settings); // Pass in the settings object we created above
How to achieve below deserialization. value in JSON sometime int and sometime it's decimal. I am working under multiple restrictions so -
can't change value as int property. It may break existing contract and this is use all around system.
have to use MyType as this is use all around system
I noticed decimal in JSON to int deserialization will throw exception.
public class MyType
{
[JsonProperty("type")]
[Required]
public string Type { get; set; }
[JsonProperty("value")] // existing field
public int Value { get; set; }
[JsonProperty("value")] // new field planning to add for new data
public decimal Value2 { get; set; }
}
You could leverage a custom JsonConverter. Decorate your class:
[JsonConverter(typeof(CustomConverter))]
public class MyType
{
[JsonProperty("type")]
[Required]
public string Type { get; set; }
[JsonProperty("value")] // existing field
public int Value { get; set; }
// new field planning to add for new data
public decimal Value2 { get; set; }
}
Where CustomConverter is defined (roughly):
public class CustomConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JToken.Load(reader) as JObject;
MyType myType = new MyType();
myType.Type = jObject.GetValue("type").ToString();
JToken tokenValue = jObject["value"];
if (tokenValue.Type == JTokenType.Integer)
{
myType.Value = int.Parse(tokenValue.ToString());
}
else if (tokenValue.Type == JTokenType.Float) {
myType.Value2 = decimal.Parse(tokenValue.ToString());
}
return myType;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
Note that one of Value and Value2 is implicitly set to 0 whereas the other property contains deserialized value.
To test the solution execute:
string json = #"{type:""Type1"",value: 27.99}";
MyType temp = JsonConvert.DeserializeObject<MyType>(json);
I'm making requests to an API service that returns dates as JSON in the following form:
{
"dateCreated": {
"date":8,
"day":4,
"hours":22,
"minutes":44,
"month":10,
"nanos":241000000,
"seconds":46,
"time":1194590686241,
"timezoneOffset":480,
"year":107
}
}
Here time is the number of milliseconds since the Unix epoch, so that's really the only property I want to pay attention to.
Would it be possible to write a custom Json.NET converter that deserializes this to something like
public class ApiResponse
{
public ApiResponse(DateTimeOffset dateCreated)
{
DateCreated = dateCreated;
}
public DateTimeOffset DateCreated { get; }
}
(The converter would call DateTimeOffset.FromUnixTimeMilliseconds on the object's time property.)
Here is the code I have so far:
public class ApiDateConverter : JsonConverter
{
public override bool CanWrite => false;
public override bool CanConvert(Type objectType)
{
return typeof(DateTimeOffset) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// What type is existingValue here? How do I get existingValue.time?
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
}
Option 1: Your ApiDateConverter can be completed by:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
dynamic jObject = JObject.Load(reader);
return DateTimeOffset.FromUnixTimeMilliseconds(jObject.time.Value);
}
Option 2: Without using custom converter:
public class DateSerialized
{
public int date { get; set; }
public int day { get; set; }
public int hours { get; set; }
public int minutes { get; set; }
public int month { get; set; }
public int nanos { get; set; }
public int seconds { get; set; }
public long time { get; set; }
public int timezoneOffset { get; set; }
public int year { get; set; }
}
public class ApiResponse
{
[JsonIgnore]
public DateTimeOffset DateCreated => DateTimeOffset.FromUnixTimeMilliseconds(DateSerialized.time);
[JsonProperty("dateCreated")]
private DateSerialized DateSerialized { get; set; }
}
It is certainly possible to write a Json.Net converter to translate the JSON directly into your domain object, but I don't advise this.
Instead, I would suggest you create a DTO (data transfer object) that mirrors the data:
public class DateDto
{
public long Time { get; set; }
}
Then have Json.Net deserialize into this object.
Once you have that, you can create a pure .Net converter that maps the DateDto to a DateTime or whatever structure you want (supposing maybe that you are using the NodaTime library instead of .Net's DateTime).
By doing this, you save yourself the headache of implementing custom deserialization logic, and you're following the S in SOLID: Single Responsibility Principle. You have an object whose purpose is solely to represent the data, and a converter to translate that into whatever your application uses.
It also increases the testability of your code.
I´m deserializing a JSON response and there is a object that could be an array or a simple object, for example:
{
"ValueAdds":{
"#size":3,
"ValueAdd":[
{
"description":"desc 1"
},
{
"description":"desc 1"
}
]
}
}
and the other case:
{
"ValueAdds": {
"#size": "1",
"ValueAdd": {
"description": "Internet inalámbrico gratuito"
}
}
}
The entities:
public ValueAddsWrap
{
public ValueAddBase ValueAdds { set; get; }
}
public class ValueAddBase
{
public ValueAdd[] ValueAdd { set; get; }
}
public class ValueAdd
{
public string description { set; get; }
}
I´m getting an exception when I receive a simple object. How could I design the entities in order to deserialize an array and a simple object?
I´m using Newtonsoft:
T resp_object = JsonConvert.DeserializeObject<T>(json);
and the exception:
An exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll but was not handled in user code
Additional information: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'rce.bookings.business.expedia.Responses.ValueAdd[]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
You really can't do what I think you're asking. In the first case you've got a collection inside the object you're getting back.
public class ValueAdd
{
public string description { get; set; }
}
public class ValueAdds
{
public int size { get; set; }
public List<ValueAdd> ValueAdd { get; set; }
}
public class RootObject
{
public ValueAdds ValueAdds { get; set; }
}
In the second case you've got a single instance of an object.
public class ValueAdd
{
public string description { get; set; }
}
public class ValueAdds
{
public string size { get; set; }
public ValueAdd ValueAdd { get; set; }
}
public class RootObject
{
public ValueAdds ValueAdds { get; set; }
}
You might be able to get away with using a dynamic for the ValueAdd member, but that presents a whole bunch of annoying problems of its own as you'll still have to figure out whether it contains a collection or a single instance.
Realistically I see you have two options:
Option #1: Figure out if you've got the version with the collection or the single instance and deserialize into the appropriate type.
Option #2: Can you contact the author of the API and have them send back a consistent JSON structure? That's a lousy API if it changes the structure based on whether there are one or more than one ValueAdd objects.
With a JsonConverter:
public class ValueAddsConverter : 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 valueAdds = new ValueAdds();
var jo = (JObject)JObject.Load(reader)["ValueAdds"];
valueAdds.Size = Int32.Parse((string)jo["#size"]);
if (valueAdds.Size > 1)
{
valueAdds.ValueAdd = jo["ValueAdd"].Children().Select(x => x.ToObject<ValueAdd>());
}
else
{
valueAdds.ValueAdd = new List<ValueAdd>{jo["ValueAdd"].ToObject<ValueAdd>()};
}
return valueAdds;
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof (ValueAdds));
}
}
public class ValueAdd
{
[JsonProperty(PropertyName = "description")]
public string Description { get; set; }
}
[JsonConverter(typeof (ValueAddsConverter))]
public class ValueAdds
{
public int Size { get; set; }
public IEnumerable<ValueAdd> ValueAdd { get; set; }
}
I have the following json string:
{"Visits":[true,"DockedOnly","leftZone","0","500",0,0,0],
"Weather":[true,"DockedOnly","leftZone","0","0",0,0,1],
"ContactUs":[true,"DockedOnly","leftZone","0","317",0,0,2],
"Birthdays":[true,"DockedOnly","middleZone","0","0",0,0,0],
"Reminders":[true,"DockedOnly","middleZone","0","145",0,0,1],
"Messages":[true,"DockedOnly","middleZone","0","0",0,0,2],
"Availability":[true,"DockedOnly","middleZone","0","0",0,0,3],
"Settings":[false,"DockedOnly","leftzone","0","155",0,0,0]}
Is there anyway to deserialize to something like the following?
[Serializable]
public class WidgetProps
{
public bool Visible { get; set; }
public string DockState { get; set; }
public string Zone { get; set; }
public string Top { get; set; }
public string Left { get; set; }
public int UnusedA { get; set; }
public int UnusedB { get; set; }
public int Position { get; set; }
}
[Serializable]
public class WidgetLayout
{
public WidgetProps Visits { get; set; }
public WidgetProps Weather { get; set; }
public WidgetProps ContactUs { get; set; }
public WidgetProps Birthdays { get; set; }
public WidgetProps Reminders { get; set; }
public WidgetProps Messages { get; set; }
public WidgetProps Availability { get; set; }
public WidgetProps Settings { get; set; }
}
or
public class Widget
{
public string WidgetName { get; set; }
public WidgetProps props { get; set; }
}
List<Widget> MyWidgets;
I am given the json string so I can't change how it is given to me but maybe I could tinker with it after I get it so it will work.
I tried:
string s = "{\"Visits\":[true,\"DockedOnly\",\"leftZone\",\"0\",\"500\",0,0,0],\"Weather\":[true,\"DockedOnly\",\"leftZone\",\"0\",\"0\",0,0,1],\"ContactUs\":[true,\"DockedOnly\",\"leftZone\",\"0\",\"317\",0,0,2],\"Birthdays\":[true,\"DockedOnly\",\"middleZone\",\"0\",\"0\",0,0,0],\"Reminders\":[true,\"DockedOnly\",\"middleZone\",\"0\",\"145\",0,0,1],\"Messages\":[true,\"DockedOnly\",\"middleZone\",\"0\",\"0\",0,0,2],\"Availability\":[true,\"DockedOnly\",\"middleZone\",\"0\",\"0\",0,0,3],\"Settings\":[false,\"DockedOnly\",\"leftzone\",\"0\",\"155\",0,0,0]}}";
var sd = new JavaScriptSerializer().Deserialize < List<Widget>>(s);
and
var sd = new JavaScriptSerializer().Deserialize < WidgetLayout >(s);
This isn't working because you're trying to deserialize an array into an object. The json.NET deserializer will not be able make that conversion.
Because your json arrays have multiple types in them you'll have to deserialize into the smallest common denominator, in this case, object. From there I would recommend writing a method to assign each index to it's corresponding property in WidgetProps.
So basically, define this constructor;
public WidgetProps(object[] props)
{
Visible = (bool)props[0];
DockState = (string)props[1];
// ext
}
I would have something like a WidgetDirty class that I do the initial deserilization into. From there you can create a new instance of WidgetLayout by instantiating each of it's properties like myWidgetLayoutInstance.Visits = new WidgetProp(myWidgetDirtyInstance.Visits); I'd probably hide this mess in a WidgetLayout constructor that takes a WidgetDirty as it's only arg.
yes it's disgusting... but I don't know of any real alternatives because that json's design just isn't very compatible with the C# language. If you're strongly apposed to this I might look at the dynamic type. I haven't used it in C# and probably never will, but I know that deserializing that in a dynamic language like PHP would be no trouble at all.
This doesn't work, because array is normally not deserialized as an object. If possible, I think you should fix the JSON. If you can't do that, and you're using JSON.NET, you can create JsonConverter for WidgetProps that manually converts the array to the object:
class WidgetPropsConverter : JsonConverter
{
public override void WriteJson(
JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
public override object ReadJson(
JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var array = serializer.Deserialize<object[]>(reader);
return new WidgetProps
{
Visible = (bool)array[0],
DockState = (string)array[1],
Zone = (string)array[2],
Top = (string)array[3],
Left = (string)array[4],
Position = (int)(long)array[7]
};
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(WidgetProps);
}
}
You would then use it like this:
var result = JsonConvert.DeserializeObject<WidgetLayout>(
jsonString, new WidgetPropsConverter());