问题
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