Reputation: 172
The issue I’m having is how to Deserialize the following JSON. The values of Answer is sometimes NULL, true, an integer, boolean, or contain another list of JSON (id, description and so on).
The first step I did was to copy and paste special as class in Visual Studio. This provided me with the following Questions class.
I then tried to deserialize this (following C# - where rawResponse is the JSON). However, I get the “Newtonsoft.Json.JsonSerializationException: 'Cannot deserialize the current JSON array (e.g. [1,2,3]) into type Applicated.Questions because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly....“
I understand it’s something to do with it not mapping correctly.
Therefore, tried to put this all in a foreach loop by saving the response in a dynamic variable (note I did remove the Property1 from Questions class for this). However, some results within Answer store the string Id, Description and so on in the Answer field. Is there an easier way of doing this, I seem to have reached a mental block?
JSON:
[
{
"Answer": true,
"QuestionId": 55,
"Title": "Are you Married?",
"AnswerType": "Boolean"
},
{
"Answer": {
"Id": "1",
"Description": "Female",
"Reference": "F",
"ArchiveDate": null,
"ParentId": null,
"OptionType": {
"Id": 40,
"Type": "dropdown"
}
},
"QuestionId": 778,
"Title": "Gender",
"AnswerType": "Option”
}
]
CLASS:
public class Questions
{
public Class1[] Property1 { get; set; }
}
public class Class1
{
public object Answer { get; set; }
public int QuestionId { get; set; }
public string Title { get; set; }
public string AnswerType { get; set; }
}
C#:
Questions result = JsonConvert.DeserializeObject<Questions>(rawResponse);
C# Dynamic Foreach
dynamic result = JsonConvert.DeserializeObject(rawResponse);
var lstQuestionObjects = new List<Questions>();
foreach(var obj in result)
{
lstQuestionObjects.Add(new Questions()
{
Answer = (obj.Answer !=null) ? obj.Answer.ToString() :"",
QuestionId = int.Parse(obj.QuestionId.ToString()),
Title = (obj.Title != null) ? obj.Title.ToString() : "",
AnswerType = (obj.AnswerType != null) ? obj.AnswerType.ToString() : ""
});
}
Upvotes: 3
Views: 1515
Reputation: 16554
It depends on what level of interoperability you require, but the first thing to recognise is that in JSON.Net all tokens in a JSON file internally inherit from JToken, so rather than using an untyped object reference in your model, you will get better default deserialization support by typing your dynamic property as a JToken
.
This simple solution shows a helper method on the Question
class that resolves the Answer as a strongly typed object reference. Depending on your implementation requirements, you may not even need strongly typed access to the answer, however this method allows you to post-process and troubleshoot the records values after the deserialization has completed.
https://dotnetfiddle.net/FwFyOU
List<Question> questions = JsonConvert.DeserializeObject<List<Question>>(GetJSON());
FiddleHelper.WriteTable(questions);
foreach(var question in questions)
{
object answer = question.GetTypedAnswer();
Console.WriteLine("Question: [{0}] {1}: ({2}) {3}", question.QuestionId, question.Title, answer.GetType().Name, answer );
}
NOTE: There is no need for a class called Questions
to be defined at all.
public class Question
{
public JToken Answer { get; set; }
public int QuestionId { get; set; }
public string Title { get; set; }
public string AnswerType { get; set; }
public object GetTypedAnswer ()
{
switch(AnswerType.ToLower())
{
case "boolean":
return Answer.Value<bool>();
case "string":
return Answer.Value<string>();
case "option":
return (Answer as JObject).ToObject<Option>();
default:
return Answer;
}
}
}
public class Option
{
public string Id { get; set; }
public string Description { get; set; }
public string Reference { get; set; }
public DateTime? ArchiveDate { get; set; }
public string ParentId { get; set; }
public OptionType OptionType { get;set; }
public override string ToString()
{
return String.Format("[{0}] {1} ({2})", Reference, Description, OptionType.Type);
}
}
public class OptionType
{
public int Id { get; set; }
public string Type { get; set; }
}
Answer | QuestionId | Title | AnswerType |
---|---|---|---|
True | 55 | Are you Married? | Boolean |
{ "Id": "1", "Description": "Female", "Reference": "F", "ArchiveDate": null, "ParentId": null, "OptionType": { "Id": 40, "Type": "dropdown" } } | 778 | Gender | Option |
Question: [55] Are you Married?: (Boolean) True
Question: [56] What is your name?: (String) Mr John Doe
Question: [778] Gender: (Option) [F] Female (dropdown)
Another solution is to use a JsonConverter for the Answer
property, however in this instance the metadata required to resolve the type is defined in the parent token, and not within the value itself which means that your custom converter will need to resolve the type through trial and error which could be very slow if there are many types to compare against.
A compromise would be to use a custom converter for the Question
class itself, choosing one method over the other depends on your needs and how many types need to be resolved, if they need to be resolved at all.
https://dotnetfiddle.net/m46NaX
List<Question> questions = JsonConvert.DeserializeObject<List<Question>>(GetJSON());
FiddleHelper.WriteTable(questions);
foreach(var question in questions)
{
Console.WriteLine("Question: [{0}] {1}: ({2}) {3}", question.QuestionId, question.Title, question.Answer.GetType().Name, question.Answer );
}
[JsonConverter(typeof(QuestionConverter))]
public class Question
{
public object Answer { get; set; }
public int QuestionId { get; set; }
public string Title { get; set; }
public string AnswerType { get; set; }
}
public class QuestionConverter : JsonConverter<Question>
{
public override Question ReadJson(JsonReader reader, Type objectType, Question existingValue, bool hasExistingValue, JsonSerializer serializer)
{
JObject item = JObject.Load(reader);
var result = new Question();
result.QuestionId = item.GetValue(nameof(result.QuestionId)).Value<int>();
result.Title = item.GetValue(nameof(result.Title)).Value<string>();
result.AnswerType = item.GetValue(nameof(result.AnswerType)).Value<string>();
var answer = item.GetValue(nameof(result.Answer));
switch(result.AnswerType.ToLower())
{
case "boolean":
result.Answer = answer.Value<bool>();
break;
case "string":
result.Answer = answer.Value<string>();
break;
case "option":
result.Answer = (answer as JObject).ToObject<Option>();
break;
default:
result.Answer = answer;
break;
}
return result;
}
public override bool CanWrite{ get => false; }
public override void WriteJson(JsonWriter writer, Question value, JsonSerializer serializer) { }
}
public class Option
{
public string Id { get; set; }
public string Description { get; set; }
public string Reference { get; set; }
public DateTime? ArchiveDate { get; set; }
public string ParentId { get; set; }
public OptionType OptionType { get;set; }
public override string ToString()
{
return String.Format("[{0}] {1} ({2})", Reference, Description, OptionType.Type);
}
}
public class OptionType
{
public int Id { get; set; }
public string Type { get; set; }
}
Upvotes: 6
Reputation: 442
Looks like your json is array, so you need to call Class1[]
:
var result = JsonConvert.DeserializeObject<Class1[]>(rawResponse);
Add implement custom AnswerConverter to deserialize to different object types:
public class Class1
{
[JsonConverter(typeof(AnswerConverter))]
public object Answer { get; set; }
// ... rest of props
}
UPD: If you want Answer to be converted dependent on AnswerType property, you need JsonConverter for parent type (Class1/Question), not for Answer:
void Deserialize()
{
var questions = JsonConvert.DeserializeObject<Question[]>(rawResponse);
}
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
class QuestionConverter : JsonConverter
{
public override bool CanRead => true;
public override bool CanWrite => false;
public override bool CanConvert(Type objectType) => objectType == typeof(Question);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var question = new Question();
serializer.Populate(reader, question);
switch (question.AnswerType)
{
case "Boolean": break; // already converted to bool by Newtonsoft.Json
case "Option": question.Answer = ((JObject)question.Answer).ToObject<OptionAnswer>(); break;
default: throw new NotSupportedException(question.AnswerType);
}
return question;
}
}
[JsonConverter(typeof(QuestionConverter))]
public class Question // Class1 in your example
{
public int QuestionId { get; set; }
public string Title { get; set; }
public string AnswerType { get; set; }
public object Answer { get; set; }
}
class OptionAnswer
{
public string Id { get; set; }
public string Description { get; set; }
public string Reference { get; set; }
public string ArchiveDate { get; set; }
public string ParentId { get; set; }
public OptionAnswerType OptionType { get; set; }
}
class OptionAnswerType
{
public int Id { get; set; }
public string Type { get; set; }
}
Upvotes: 1