Newtonsoft JsonConverter to convert a string or object to object

Viewed 1114

I have a requirement for some deserialization I'm trying to handle where I could have these potential inputs:

{
    "value": "a string"
}

-- or --

{
    "value": {
        "text": "a string"
        // there are other properties, but for successful deserialization I only need text present
    }
}

And I expect it to be able to successfully convert to the object, MyObject:

public class MyObject
{
    [JsonProperty("text")
    public string Text { get; set; }
}

So far this is what I have in my converter. This case works fine when it's a string (although not very efficient because I'm throwing an exception to catch a failed deserialization). However, when it's an object the reader throws an exception and I'm uncertain of how to handle it.

public class MyObjectConverter : JsonConverter
{
    public override bool CanWrite { get => false; }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(string) || objectType == typeof(MyObject);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var value = reader.Value?.ToString();
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        try
        {
            return JObject.Parse(value).ToObject<MyObject>();
        }
        catch (Exception)
        {
            return new MyObject
            {
                Text = value
            };
        }
    }
}

perhaps I'm there is already a nice way to do this that I'm unaware of? If not, how can I determine whether my input is a string or an object to be able to return the object I care about?

SOLUTION:

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.String)
        {
            return new MyObject
            {
                Text = reader.Value?.ToString()
            };
        }
        else if (reader.TokenType == JsonToken.StartObject)
        {
            JObject obj = JObject.Load(reader);
            return obj.ToObject<MyObject>();
        }
        else
        {
            return null;
        }
    }
1 Answers

Your Object isn't structured like the input your expecting. For the second case, MyObject would need to look like this:

// this is ugly
public class MyObject
{
    public Value value{get;set;}

    public class Value{
         public string text {get;set;}
    }
}

If you want to have just an object with a single Text property like you currently do, you could do something like this:

public override object ReadJson(JsonReader reader, Type objectType, object 
    existingValue, JsonSerializer serializer)
{
    JObject obj = JObject.Load(reader);
    var value = obj["value"];

    if(value is JObject)  // this will be true if the value property is a nested structure
       return new MyObject(){Text=value["text"]};  // could also do value.ToObject<MyObject>() if you need more properties
    else
       return new MyObject(){Text=value};
}
Related