Home > database >  How Can I deserialize a json into a strucuture?
How Can I deserialize a json into a strucuture?

Time:10-27

I have a class mapping like that:

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?

CodePudding user response:

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

    }
}

CodePudding user response:

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; }
}
  • Instead of having a middle man (ContentStructure) rather than you can have two separate Settings versions
  • The common fields are defined inside the abstract base class

Now, you can use these two versioned class 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");
}

CodePudding user response:

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.

CodePudding user response:

You can use Newtonsoft to do your (de)serialization: https://www.newtonsoft.com/json/help/html/SerializeObject.htm

Or, you can use the JsonSerializer: https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to?pivots=dotnet-5-0

  • Related