Reputation: 157
I have a class mapping like this:
public class Settings
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("content")]
public ContentStructure Content { get; set; }
}
public struct ContentStructure
{
public Content ContentClass;
public string ContentString;
public static implicit operator ContentStructure(Content content) => new ContentStructure { ContentClass = content };
public static implicit operator ContentStructure(string @string) => new ContentStructure { ContentString = @string };
}
public class Content
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("duration")]
public long Duration { get; set; }
}
When I attempt to deserialize the following JSON string:
{
"id": "any_id",
"type": "any_type",
"content": {
"id": "any_id",
"duration": 1000
}
}
I always get the deserialized settings object with the property settings.Content.ContentClass null, but whenever my JSON string has the property "content"
as string (instead of an object) the structure field ContentString
comes correctly. What I am doing wrong? How can I can convert the JSON string above correctly?
Upvotes: 2
Views: 5933
Reputation: 22679
Yet another solution could be to make use of the JsonSchema
First let's redefine your data model:
public abstract class Settings
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
}
public class SettingsV1 : Settings
{
[JsonProperty("content")]
public string Content { get; set; }
}
public class SettingsV2 : Settings
{
[JsonProperty("content")]
public Content Content { get; set; }
}
public class Content
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("duration")]
public long Duration { get; set; }
}
ContentStructure
) rather than you can have two separate Settings
versionsNow, you can use these two versioned classes to define json schemas:
private static JSchema schemaV1;
private static JSchema schemaV2;
//...
var generator = new JSchemaGenerator();
schemaV1 = generator.Generate(typeof(SettingsV1));
schemaV2 = generator.Generate(typeof(SettingsV2));
Finally all you need to do is to do a preliminary check before calling the DeserializeObject
with the proper type:
Settings settings = null;
var semiParsed = JObject.Parse(json);
if (semiParsed.IsValid(schemaV1))
{
settings = JsonConvert.DeserializeObject<SettingsV1>(json);
}
else if (semiParsed.IsValid(schemaV2))
{
settings = JsonConvert.DeserializeObject<SettingsV2>(json);
}
else
{
throw new NotSupportedException("The provided json format is not supported");
}
Upvotes: 2
Reputation: 2280
Use a Custom JsonConverter. Modify it based on your need.
[JsonConverter(typeof(ContentStructureConverter))]
public struct ContentStructure
{
public Content ContentClass;
public string ContentString;
public static implicit operator ContentStructure(Content content) => new ContentStructure { ContentClass = content };
public static implicit operator ContentStructure(string @string) => new ContentStructure { ContentString = @string };
}
public class ContentStructureConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ContentStructure);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
ContentStructure contentStruct;
if (reader.ValueType == typeof(string))
contentStruct = reader.Value as string;
else
contentStruct = serializer.Deserialize<Content>(reader);
return contentStruct;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
ContentStructure? contentStruct = value as ContentStructure?;
if (contentStruct.HasValue && contentStruct.Value.ContentClass != null)
serializer.Serialize(writer, contentStruct.Value.ContentClass);
else
serializer.Serialize(writer, contentStruct.Value.ContentString);
}
}
Upvotes: 1
Reputation: 178
The input json is wrong for your format.
If you use this json instead,
{
"id": "any_id",
"type": "any_type",
"content": {
"ContentClass" : {
"id": "any_id",
"duration": 1000
}
}
}
This way, it will work.
settings.Content.ContentClass is three layer but your is json two layer (settings.Content). So after "content", it is looking for id and duration which ContentStructure does not have these two fields.
My reasoning is when it encounters a value type (like {"field" : "value}), it will look for value type, like string, int or double. When it encounters a json type (like {"field" : {another json here} }), it will look for class or struct.
Upvotes: 0