反序列化动态JSON
c#
我遇到的问题是如何反序列化以下 JSON。Answer 的值有时是 NULL、true、整数、布尔值或包含另一个 JSON 列表(id、描述等)。
我做的第一步是在 Visual Studio 中将 special 作为类复制和粘贴。这为我提供了以下问题类。
然后我尝试反序列化它(遵循 C# - 其中 rawResponse 是 JSON)。但是,我得到“Newtonsoft.Json.JsonSerializationException: 'Cannot deserialize the current JSON array (eg [1,2,3]) into type Applicated.Questions 因为该类型需要一个 JSON 对象(例如 {"name":"value "}) 正确反序列化......“
我知道这与它没有正确映射有关。
因此,尝试通过将响应保存在动态变量中来将所有这些都放在 foreach 循环中(注意,我确实为此从 Questions 类中删除了 Property1)。但是,Answer 中的某些结果将字符串 Id、Description 等存储在 Answer 字段中。有没有更简单的方法来做到这一点,我似乎已经达到了心理障碍?
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”
}
]
班级:
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# 动态 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() : ""
});
}
回答
这取决于您需要什么级别的互操作性,但首先要认识到的是,在 JSON.Net中,JSON 文件中的所有标记都在内部继承自 JToken,因此与其在模型中使用无类型对象引用,您将获得更好的默认值通过将动态属性键入为JToken.
这个简单的解决方案显示了Question类上的一个辅助方法,该方法将 Answer 解析为强类型对象引用。根据您的实现要求,您甚至可能不需要对答案进行强类型访问,但是此方法允许您在反序列化完成后对记录值进行后处理和故障排除。
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 );
}
模型类定义
注意:根本不需要Questions定义一个被调用的类。
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; }
}
输出:
| 回答 | 问题编号 | 标题 | 应答类型 |
|---|---|---|---|
| 真的 | 55 | 你结婚了吗? | 布尔值 |
| { "Id": "1", "Description": "Female", "Reference": "F", "ArchiveDate": null, "ParentId": null, "OptionType": { "Id": 40, "Type “: “落下” } } | 778 | 性别 | 选项 |
Question: [55] Are you Married?: (Boolean) True
Question: [56] What is your name?: (String) Mr John Doe
Question: [778] Gender: (Option) [F] Female (dropdown)
另一种解决方案是使用JsonConverter的Answer财产,但是在这种情况下所需的元数据,以解决该类型是在父令牌定义,而不是价值本身,这意味着您的自定义转换器将需要通过试验来解决的类型和如果要比较的类型很多,错误可能会非常慢。
折中方法是为Question类本身使用自定义转换器,选择一种方法而不是另一种方法取决于您的需要以及需要解析的类型数量(如果需要解析它们)。
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 );
}
Json 转换器实现
[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; }
}