Can I parse json either into a string or another concrete type as object?

心已入冬 提交于 2019-12-05 21:38:57

This problem can be solved by using a custom JsonConverter. Here is a generic version that should work for this situation:

class ObjectOrStringConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        // CanConvert is not called when the [JsonConverter] attribute is used
        return false;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Object)
        {
            return token.ToObject<T>(serializer);
        }
        return token.ToString();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

To use the converter, all you need to do is add a [JsonConverter] attribute to the property in your class that can be either a string or an object. The generic type parameter must match the type of non-string object you are expecting.

class MyClass
{
    [JsonConverter(typeof(ObjectOrStringConverter<Template>))]
    public object Template { get; set; }
}

Below is a demonstration of the converter in action:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("--- first run ---");

        string json = @"
        {
            ""Template"": ""TemplateName""
        }";

        DeserializeAndDump(json);

        Console.WriteLine("--- second run ---");

        json = @"
        {
            ""Template"": { ""Name"": ""OtherTemplate"" }
        }";

        DeserializeAndDump(json);
    }

    static void DeserializeAndDump(string json)
    {
        MyClass obj = JsonConvert.DeserializeObject<MyClass>(json);
        if (obj.Template == null)
        {
            Console.WriteLine("Template property is null");
        }
        else
        {
            Console.WriteLine("Template property is a " + obj.Template.GetType().Name);

            string name = "(unknown)";
            if (obj.Template is Template) name = ((Template)obj.Template).Name;
            else if (obj.Template is string) name = (string)obj.Template;

            Console.WriteLine("Template name is \"" + name + "\"");
        }
        Console.WriteLine();
    }
}

class Template
{
    public string Name { get; set; }
}

And here is the output from the above:

--- first run ---
Template property is a String
Template name is "TemplateName"

--- second run ---
Template property is a Template
Template name is "OtherTemplate"

Fiddle: https://dotnetfiddle.net/Lw3RaN

I don't know if you can do that, but you could go another way. Change your "Template" property to be a Template instead of an object and use a custom property of the Template class to know weither you want to serialize it as a Template or a string.

class MyClass
{
    [JsonConverter(typeof(TemplateConverter))]
    public Template Template { get; set; }
}

class Template
{
    /* Your Template class */

    public Type TypeToSerializeInto { get; private set; }
}

public class TemplateConverter : JsonConverter
{
    public override bool CanConvert(Type objectType) 
    { 
        return objectType == typeof(Template);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
        Template val = value as Template;

        writer.WriteStartObject();
        writer.WritePropertyName("Template");

        if (val.TypeToSerializeInto == typeof(Template))
            serializer.Serialize(writer, val);
        else if (val.TypeToSerializeInto == typeof(string))
            serializer.Serialize(writer, val.Name);

        writer.WriteEndObject();
    }
}
Boies Ioan

If you have a property, which type is an abstract type - like object, on de-serialization, you can know the specific type that was serialized by serializing somewhere also the name of that specific type. So your json should look like this:

{ "MyClass": { "Template": "some name", "type": "System.String" } }

This way on deserialization you can check what type was that property before the serialization (in this case String)

Another way to determine the type is by checking the json structure as you can see here: C#: Deserializing JSON when one field can be different types

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!