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);
This is my JSON
{
"type": "user_settings",
"created_at": 1610973280043,
"data": {
"user_id": 12345,
"updated_at": "2021-01-18T15:34:40+03:00"
}
}
These are my classes:
public class BaseMessage
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("created_at")]
public long CreatedAt { get; set; }
[JsonProperty("data")]
public DataDTO Data { get; set; }
public string DataJson {get; set;} // <-- Here I need string like this "{ "user_id": 12345, "updated_at": "2021-01-18T15:34:40+03:00" }"
}
public class DataDTO
{
[JsonProperty("user_id")]
public int UserId { get; set; }
[JsonProperty("updated_at")]
public DateTime? UpdatedAt { get; set; }
}
So I need parsed "data" (it works ok) and save nested JSON as a string (I don't know how).
Is there elegant way to save nested JSON into string property?
Something like this:
public class BaseMessage
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("created_at")]
public long CreatedAt { get; set; }
[JsonProperty("data")]
public DataDTO Data { get; set; }
[JsonPropertyAsString("data")] // <-- This is my fantasy :)
public string DataJson {get; set; }
}
As #dbc commented you can create a custom JsonConverter for the DataJson property, but you also should do something with your another property which is mapped from the data JSON field - Data of DataDTO type. I can propose the following solution:
1. Custom JSON Converter (I took this from #dbc's answer)
public class RawConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var raw = JRaw.Create(reader);
return raw.ToString();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var s = (string)value;
writer.WriteRawValue(s);
}
}
2. Decorate your DataJson property with the JsonConverter attribute and remove JsonPropertyAttribute for the Data property
Note that if you don't remove the JsonPropertyAttribute then it won't work, since you have two properties which are mapped from the same JSON field, and as I know this is not supported by Json.NET by default.
public class BaseMessage
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("created_at")]
public long CreatedAt { get; set; }
public DataDTO Data { get; set; }
[JsonProperty("data")]
[JsonConverter(typeof(RawConverter))]
public string DataJson {get; set;}
}
3. Update your BaseMessage class so this calculates the value of the Data property from DataJson
public class BaseMessage
{
private DataDTO data;
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("created_at")]
public long CreatedAt { get; set; }
public DataDTO Data
{
if (data == null)
{
data = JsonConvert.DeserializeObject<DataDTO>(DataJson);
}
return data;
}
[JsonProperty("data")]
[JsonConverter(typeof(RawConverter))]
public string DataJson {get; set;}
}
Note that I believe this is not the best solution and sure that there are other much better alternatives, but it might be feasible in your case.
Do you mean to use this ??
var data = Newtonsoft.Json.JsonConvert.SerializeObject(object);
if yes, install the newtonSoft packge.
This question originates from another topic which can be found at:
Extract objects from JSON array to list
The thing is that I'm receiving the following JSON response, and my JSON.NET deserializer doesn't understand it, but several other JSON validators like https://jsonlint.com say that it is valid.
[
{"value":"{\"code\":\"MO\",\"description\":\"Monday\",\"isSet\":false}","nr":1}
,{"value":"{\"code\":\"TU\",\"description\":\"Tuesday\",\"isSet\":true}","nr":2}
]
In my opinion the problem here is that the value object looks like a JSON object, but actually is a string.
JsonConvert.DeserializeObject keeps throwing errors until I remove the additional quotes (") and escaping chars.
So the question is, why is this response formatted like this? And how to tell the deserializer how to work with it? I'm sure that removing or replacing chars isn't the way to go.
Here is what i'm doing:
public class Value
{
public string code { get; set; }
public string description { get; set; }
public bool isSet { get; set; }
}
public class RootObject
{
public Value value { get; set; }
public int nr { get; set; }
}
var json = JsonConvert.DeserializeObject<List<RootObject>>(serviceResult);
The above doesn't work.
For the time being I have solved the issue this way. But I keep thinking that the above, with the deserializer, is more elegant.
JArray jArray = JArray.Parse(serviceResult);
List<Value> values = jArray.Select(x => JObject.Parse(x["value"].ToString()).ToObject<Value>()).ToList();
The easiest way to do this would be to use a custom JsonConverter, for example something like this:
public class StringToObjectConverter<T> : Newtonsoft.Json.JsonConverter
{
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
//This will only be needed if you also need to serlialise
writer.WriteRaw(JsonConvert.SerializeObject(value));
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
return JsonConvert.DeserializeObject<T>(reader.Value.ToString());
}
public override bool CanRead => true;
//We can only work with the type T, you could expand this to cope with derived types
public override bool CanConvert(Type objectType) => typeof(T) == objectType;
}
Now using these models, noting in particular the attribute on the Value property:
public class RootObject
{
[JsonConverter(typeof(StringToObjectConverter<Value>))]
public Value value { get; set; }
public int nr { get; set; }
}
public class Value
{
public string code { get; set; }
public string description { get; set; }
public bool isSet { get; set; }
}
Now it's a simple deserialisation:
var json = "....";
var rootObjects = JsonConvert.DeserializeObject<List<RootObject>>(json);
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());