反序列化动态JSON。

3
我遇到的问题是如何反序列化以下JSON。Answer的值有时为NULL、true、整数、布尔值或包含另一个JSON列表(id、description等)。
我首先尝试在Visual Studio中将其复制并特殊粘贴为类。这提供了以下Questions类。
然后我尝试反序列化它(使用C#,其中rawResponse是JSON)。但是,我收到了“Newtonsoft.Json.JsonSerializationException:'无法将当前JSON数组(例如[1,2,3])反序列化为类型Applicated.Questions,因为该类型需要一个JSON对象(例如{"name":“value”})才能正确反序列化....” 的错误信息。
我理解这与映射不正确有关。
因此,我尝试将所有内容放入foreach循环中,通过将响应保存在动态变量中(请注意,我删除了Questions类中的Property1以进行此操作)。但是,答案中的一些结果在Answer字段中存储字符串Id、Description等。是否有更简单的方法来做到这一点?我似乎已经到达了心理障碍?
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 的吗?您是否愿意进行轻微的结构更改,以使其更容易? - Chris Schaller
很遗憾,这就是API中数据的存储方式,我需要从中提取数据。如果由我决定,数据不会以这种方式存储。因此,我面临着问题。 - skippy
2个回答

6

这取决于您需要什么级别的互操作性,但首先要认识到在JSON.Net中,JSON文件中所有的标记内部都从JToken继承,因此,与其在模型中使用未经类型定义的对象引用,更好的默认反序列化支持是将您的动态属性作为JToken进行类型定义。

这个简单的解决方案显示了Question类上的一个帮助方法,它将答案解析为强类型对象引用。根据您的实现需求,您甚至可能不需要强类型访问答案,但该方法允许您在反序列化完成对记录值进行后处理和故障排除。

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; }        
}

输出:

答案 问题ID 标题 答案类型
True 55 你结婚了吗? 布尔型
{ "Id": "1", "Description": "女性", "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; }        
}

1

看起来你的json是一个数组,所以你需要调用Class1[]

var result = JsonConvert.DeserializeObject<Class1[]>(rawResponse);

添加实现自定义AnswerConverter以将反序列化到不同的对象类型:

public class Class1
{
    [JsonConverter(typeof(AnswerConverter))]
    public object Answer { get; set; }
    // ... rest of props
}

更新: 如果您希望根据AnswerType属性转换答案,则需要为父类(Class1 / Question)而不是Answer使用JsonConverter:

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; }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接