填充对象异常反序列化抽象类时出现意外的初始标记“EndObject”

MrC*_*urn 3 c# json json.net

我在 json.net 中编写了一个自定义 JSON 转换器类,但无法弄清楚为什么在反序列化时会出现以下异常。

填充对象时出现意外的初始标记“EndObject”。预期的 JSON 对象或数组。路径 '',第 1 行,位置 177。

我的项目中有其他转换器,它们的模型非常相似,它们可以正常工作,所以我不确定为什么会出现这样的问题。

这是被序列化的对象:

[JsonConverter(typeof(CreateCRMIntegrationPromptJsonConverter))]
public abstract class CreateCRMIntegrationDirectPromptBaseBindingModel
{
    public bool IncludeInOutput { get; set; }
    public string Label { get; set; }
    public string Value { get; set; }
    public IValidateCRMField Validator { get; set; }
    public string ValidatorType { get; set; }
    public CRMIntegrationDirectPromptType Type { get; set; }
}
public class CreateCRMIntegrationPromptMobilePhoneBindingModel : CreateCRMIntegrationDirectPromptBaseBindingModel
{
    public bool FormatPhoneNumber { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

和转换器

public class CreateCRMIntegrationPromptJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Models.CreateCRMIntegrationDirectPromptBaseBindingModel);
    }

    public override bool CanWrite { get { return false; } }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        try
        {
            JObject jo = JObject.Load(reader);
            JsonReader jsonReader = jo.CreateReader();
            Dictionary<string, string> values = new Dictionary<string, string>(jo.ToObject<IDictionary<string, string>>(), StringComparer.CurrentCultureIgnoreCase);
            var typeValue = values["type"].ToString();

            Models.CRMIntegrationDirectPromptType integrationPromptType = Models.CRMIntegrationDirectPromptType.Label;

            if (!Enum.TryParse(typeValue, out integrationPromptType))
            {
                integrationPromptType = Models.CRMIntegrationDirectPromptType.Label;
            }

            switch (integrationPromptType)
            {
                .........
                case Models.CRMIntegrationDirectPromptType.MobilePhone:
                    Models.CreateCRMIntegrationPromptMobilePhoneBindingModel cRMIntegrationPromptMobilePhoneReturnModel = new Models.CreateCRMIntegrationPromptMobilePhoneBindingModel();
                    serializer.Populate(reader, cRMIntegrationPromptMobilePhoneReturnModel);
                    return cRMIntegrationPromptMobilePhoneReturnModel;
                .........
            }

        }
        catch(Exception ex)
        {
            Models.CreateCRMIntegrationPromptLabelBindingModel cRMIntegrationPromptLabelReturnModelDefault = new Models.CreateCRMIntegrationPromptLabelBindingModel();
            cRMIntegrationPromptLabelReturnModelDefault.IncludeInOutput = false;
            cRMIntegrationPromptLabelReturnModelDefault.Label = string.Empty;
            cRMIntegrationPromptLabelReturnModelDefault.Type = Models.CRMIntegrationDirectPromptType.Label;
            return cRMIntegrationPromptLabelReturnModelDefault;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当我使用此代码进行测试时,我可以捕获异常

var obj = new CreateCRMIntegrationPromptMobilePhoneBindingModel();
obj.IncludeInOutput = true;
obj.FormatPhoneNumber = true;
obj.Label = "Test";
obj.ValidatorType = "Answer1APILib.CRMIntegration.ValidateCRMField_NonRequired";
obj.Type = CRMIntegrationDirectPromptType.Label;
obj.Value = "";
var test = JsonConvert.SerializeObject(obj);

var output = JsonConvert.DeserializeObject<CreateCRMIntegrationDirectPromptBaseBindingModel>(test);
Run Code Online (Sandbox Code Playgroud)

这是序列化返回的JSON

{  
   "FormatPhoneNumber":true,
   "IncludeInOutput":true,
   "Label":"Test",
   "Value":"",
   "Validator":null,
   "ValidatorType":"Answer1APILib.CRMIntegration.ValidateCRMField_NonRequired",
   "Type":0
}
Run Code Online (Sandbox Code Playgroud)

dbc*_*dbc 5

您需要传递jsonReadertoserializer.Populate()而不是传入的reader。或者jsonReader完全消除变量并传入jo.CreateReader()

serializer.Populate(jo.CreateReader(), cRMIntegrationPromptMobilePhoneReturnModel);
Run Code Online (Sandbox Code Playgroud)

您需要这样做是因为您之前在传入的初始位置加载了对象JsonReader readerJObject jo

JObject jo = JObject.Load(reader);
Run Code Online (Sandbox Code Playgroud)

因此,传入reader已经通过对象前进到接下来发生的任何事情。使用reader第二次填充您的模型将进一步推进读者,最终导致Unexpected initial token 'EndObject'您看到。

您可能还想检查传入的 JSON 令牌是否null在将其作为对象加载之前:

if (reader.TokenType == JsonToken.Null)
    return null;
JObject jo = JObject.Load(reader);
Run Code Online (Sandbox Code Playgroud)

由于null在JSON文件值实际上加载为非空JValueJValue.Type等于JTokenType.Null,试图加载这样的令牌作为一个JObject将失败。

(最后,我不确定我是否会ReadJson()自己处理异常。Newtonsoft 已经有一个处理异常机制,如果你在内部捕获并吞下所有异常ReadJson()而不是使用该机制,那么在阅读格式错误、截断的 JSON 文件。不过,这不是您所看到的问题的主要原因。)

因此,一个固定版本的ReadJson()看起来像:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    if (reader.TokenType == JsonToken.Null)
        return null;                
    var jo = JObject.Load(reader);

    var typeValue = (string)jo.GetValue("Type", StringComparison.OrdinalIgnoreCase);            
    Models.CRMIntegrationDirectPromptType integrationPromptType;
    if (!Enum.TryParse(typeValue, out integrationPromptType))
    {
        integrationPromptType = Models.CRMIntegrationDirectPromptType.Label;
    }

    Models.CreateCRMIntegrationDirectPromptBaseBindingModel model;
    switch (integrationPromptType)
    {
        case Models.CRMIntegrationDirectPromptType.MobilePhone:
            model = new Models.CreateCRMIntegrationPromptMobilePhoneBindingModel();
            break;

        case Models.CRMIntegrationDirectPromptType.Label:
            model = new Models.CreateCRMIntegrationPromptLabelBindingModel();
            break;

        // Add other cases as required.

        default:
            throw new JsonSerializationException(typeValue);
    }

    serializer.Populate(jo.CreateReader(), model);
    return model;
}
Run Code Online (Sandbox Code Playgroud)

工作示例 .Net fiddle在这里