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.
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; }
}
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
}