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

混江龙づ霸主 提交于 2019-12-07 16:59:24

问题


I'd like to have property of type object that can be either a string or Template type.

Is it possible to tell Json.NET to parse something into one of several specified types?

class MyClass
{
    public object Template { get; set; }
}

where Template = "TemplateName"

{
    "Template": "TemplateName"
}

or Template = new Template()

{
    "Template": { "Name": "OtherTamplate", ... }
}

UPDATE:

I tried to follow @krillgar' advice and create a custom JsonConverter but unfortunatelly the CanConvert method receives only the target type, in this case object. This information is not enough to tell wheter it can be deserialized (in case I had other object properties). I guess I need it to be a Template after all or create a derived type like TemplateReference or something:

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

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

    public override bool CanConvert(Type objectType)
    {
        // objectType = typeof(object)
        throw new NotImplementedException();
    }
}

Configuration = JsonConvert.DeserializeObject<MyClass>(text, new myconverter());

Disclaimer

This question has once been closed as a duplicate of How to deserialize a JSON property that can be two different data types using Json.NET. Because at the time of writing my question I hadn't known that there already was a similar one I'd like to clarify the difference between them to prevent it from being closed in future:

The other question is about how to deserialize different values into a concrete type whereas mine is about deserializing different values into an object. It might seem to be the same at the first look because in both examples only the type of the property is different but it has a huge impact on the overall application design. It's important for me that I can use an object to store different specialized types rather then one type having multiple responsibilities.


回答1:


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




回答2:


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



回答3:


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



来源:https://stackoverflow.com/questions/33080843/can-i-parse-json-either-into-a-string-or-another-concrete-type-as-object

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