Home > Back-end >  deserializing a json property with string or int content
deserializing a json property with string or int content

Time:11-19

I am trying to deserialize an object with .net builtin functions.
lets see the array "attributes" which I am trying to deserialize:

"attributes": [
{
    "trait_type": "Subseries",
    "value": "Templar Order"
},
{
    "trait_type": "Colorfulness",
    "value": 2,
    "min_value": 1,
    "max_value": 5
},
{
    "trait_type": "Style",
    "value": "CGI"
},
{
    "trait_type": "Material",
    "value": "Steel"
},
{
    "trait_type": "Special Effects",
    "value": "Rare"
},
{
    "trait_type": "Background",
    "value": "Rare"
}],

As you can see, an attribute always has a trait_type and a value.
value can be of type string or int.
min and max value are optional and always of type int.

What I am struggling with is the field "value". I tried to make a class from it, but the json deserializer wont just cast an int into a string (which I would be fine with)

public class MetadataAttribute
{
    public MetadataAttribute(string Trait_Type, string Value)
    {
        trait_type = Trait_Type;
        value = Value;
    }
    public MetadataAttribute(string Trait_Type, int Value, int? Min_Value = null, int? Max_Value = null)
    {
        trait_type = Trait_Type;
        value = Value.ToString();
        min_value = Min_Value;
        max_value = Max_Value;
    }
    public MetadataAttribute() { }
    /// <summary>
    ///  the attribute name, eg sharpness
    /// </summary>
    public string trait_type { get; set; }
    /// <summary>
    /// the value of the attribute, eg 10
    /// </summary>
    public string value { get; set; }
    /// <summary>
    /// optional: the minimum value atribute to provide a possible range
    /// </summary>
    public int? min_value{get;set;}
    /// <summary>
    /// optional: the maximum value attribute to provide a possible range
    /// </summary>
    public int? max_value { get; set; }
}

current deserialize function (works when there is no int in value)

public static Metadata Load(string path)
{
    FileInfo testFile = new FileInfo(path);
    string text = File.ReadAllText(testFile.FullName);
    Metadata json = JsonSerializer.Deserialize<Metadata>(text);
    return json;
}

Hiw do I resolve this ambiguity?

CodePudding user response:

Disclaimer: This solution is uses Newtonsoft Json not System.Text.Json.

If you can define two data models for example like this:

abstract class TraitInfo
{
    [JsonProperty("trait_type")]
    public string TraitType { get; set; }
    [JsonProperty("value")]
    public virtual object Value { get; set; }
}

class TraitString : TraitInfo
{
    public virtual string Value { get; set; }
}

class TraitNumber: TraitInfo
{
    public virtual int Value { get; set; }
    [JsonProperty("min_value")]
    public int MinValue { get; set; }
    [JsonProperty("max_value")]
    public int MaxValue { get; set; }
}

class Root
{
    [JsonProperty("attributes")]
    public List<TraitInfo> Traits { get; set; }
}

then you can create a JsonConverter for TraitInfo

class TraitInfoConverter : JsonConverter<TraitInfo>
{
    public override TraitInfo? ReadJson(JsonReader reader, Type objectType, TraitInfo? existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        var semiParsed = JObject.Load(reader);
        var value = semiParsed["value"];
        return value.Type switch
        {
            JTokenType.String => semiParsed.ToObject<TraitString>(),
            JTokenType.Integer => semiParsed.ToObject<TraitNumber>(),
            _ => throw new NotSupportedException()
        };
    }

    public override void WriteJson(JsonWriter writer, TraitInfo? value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

and during deserialization you can specify this converter

var result = JsonConvert.DeserializeObject<Root>(json, new JsonSerializerSettings { Converters = { new TraitInfoConverter() } });

Please note that if you use JsonConverterAttribute on the TraitInfo then the ReadJson will be in an infinite loop.


DotnetFiddle link


UPDATE #1

As Evk has pointed out the Value properties in the derived classes are working in a bit clumsy way. So, rather than inheriting the Value and redefining them, it might make sense to define them on the derived class level

abstract class TraitInfo
{
    [JsonProperty("trait_type")]
    public string TraitType { get; set; }
}

class TraitString : TraitInfo
{
    public string Value { get; set; }
}

class TraitNumber: TraitInfo
{
    public int Value { get; set; }
    [JsonProperty("min_value")]
    public int MinValue { get; set; }
    [JsonProperty("max_value")]
    public int MaxValue { get; set; }
}

class Root
{
    [JsonProperty("attributes")]
    public List<TraitInfo> Traits { get; set; }
}

New dotnet fiddle link

CodePudding user response:

it is better to use Newtonsoft.Json but you can change the class to avoid a custom serializer

public class MetadataAttribute
{
    //.... your code

        
    [System.Text.Json.Serialization.JsonPropertyName("value")]
    public object _value
    {
        get
        {
            if (int.TryParse(value, out var intValue)) return intValue;
            return this.value;
        }
        set { this.value = value.ToString(); }
    }
        
    /// <summary>
    /// the value of the attribute, eg 10
    /// </summary>
    [System.Text.Json.Serialization.JsonIgnore]
    public string value { get; set; }
    
    // ...your code
}
  • Related