Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected initial token 'EndObject' when populating object exception deserializing abstract class

Tags:

json

c#

json.net

I wrote a custom JSON converter class in json.net and cannot figure out why I am getting the following exception when I deserialize with it.

Unexpected initial token 'EndObject' when populating object. Expected JSON object or array. Path '', line 1, position 177.

I have other converters in my project which are modeled very similar which work without issue so I am unsure why this is being such a problem.

Here is the object being serialized:

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

And the converter

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

When I test with this code I can catch the exception

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

Here is the JSON returned by the serialization

{  
   "FormatPhoneNumber":true,
   "IncludeInOutput":true,
   "Label":"Test",
   "Value":"",
   "Validator":null,
   "ValidatorType":"Answer1APILib.CRMIntegration.ValidateCRMField_NonRequired",
   "Type":0
}
like image 405
MrChrisOsburn Avatar asked Nov 02 '25 21:11

MrChrisOsburn


1 Answers

You need to pass the jsonReader to serializer.Populate() rather than the incoming reader. Or eliminate the variable jsonReader entirely and pass in jo.CreateReader():

serializer.Populate(jo.CreateReader(), cRMIntegrationPromptMobilePhoneReturnModel);

You need to do this because you previously loaded the object at the initial location of the incoming JsonReader reader into JObject jo:

JObject jo = JObject.Load(reader);

Thus the incoming reader has been advanced past the object and on to whatever comes next. Using the reader a second time to populate you model will advance the reader further still, eventually causing the Unexpected initial token 'EndObject' you are seeing.

You might also want to check to see whether the incoming JSON token is null before loading it as an object:

if (reader.TokenType == JsonToken.Null)
    return null;
JObject jo = JObject.Load(reader);

Since a null value in JSON file is actually loaded as a non-null JValue with JValue.Type equal to JTokenType.Null, attempting to load such a token as a JObject will fail.

(Finally, I'm not sure I would handle exceptions in ReadJson() itself. Newtonsoft already has a mechanism for handling exceptions and if you catch and swallow all exceptions inside ReadJson() rather than using the mechanism there's a chance you could fall into an infinite loop when reading a malformed, truncated JSON file. This is not the primary cause of the problem you are seeing though.)

Thus a fixed version of ReadJson() would look like:

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

Working sample .Net fiddle here.

like image 68
dbc Avatar answered Nov 05 '25 12:11

dbc



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!