【问题标题】:deserializing a json property with string or int content使用字符串或 int 内容反序列化 json 属性
【发布时间】:2022-11-19 01:23:42
【问题描述】:

我正在尝试使用 .net 内置函数反序列化一个对象。
让我们看看我试图反序列化的数组“属性”:

"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"
}],

如您所见,一个属性总是有一个 trait_type 和一个值。
value 可以是 string 或 int 类型。
最小值和最大值是可选的,并且始终是 int 类型。

我正在努力解决的是“价值”领域。我试图从中创建一个类,但 json 反序列化器不会将一个 int 转换为一个字符串(我会很好)

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

当前的反序列化函数(当值中没有 int 时有效)

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

我该如何解决这种歧义?

【问题讨论】:

  • 您能否将 C# value 属性的类型更改为其他类型,例如 object
  • 您需要定义单一数据模型吗?或者你能定义两个吗?
  • 复制 app.quicktype.io 中的 Json,包装成 { },并删除最后一个逗号。如果每种特征类型都应该成为一种类型,那么您可以拥有更具体的东西
  • 如果转换字符串没问题,您可以有一个自定义属性来处理反序列化部分的值,并始终从该值生成字符串。
  • @PeterCsala 我可以定义 2 个数据模型,但我不知道如何将它集成到我的 C# 代码中

标签: c# json .net serialization system.text.json


【解决方案1】:

免责声明:此解决方案使用 Newtonsoft Json 而不是 System.Text.Json。

如果您可以像这样定义两个数据模型:

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

那么你可以为TraitInfo创建一个JsonConverter

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

在反序列化期间,您可以指定此转换器

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

请注意,如果您在TraitInfo 上使用JsonConverterAttribute,那么ReadJson 将处于无限循环中。


DotnetFiddle link


更新 #1

正如 Evk 指出的那样,派生类中的 Value 属性的工作方式有点笨拙。因此,与其继承 Value 并重新定义它们,不如在派生类级别定义它们更有意义

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

【讨论】:

  • 请注意,您正在使用的是危险的类层次结构,通过在子类中隐藏具有相同名称属性的父 Value 属性。反序列化后只设置父object Value属性,子属性为空。因此,例如在您的小提琴中 - result.Traits[0].Value 生成 Templar Order。然而,((TraitString)result.Traits[0]).Value(同一个对象)产生空值。这真是糟糕的设计。
  • @Evk 不错,让我修改我的解决方案。
【解决方案2】:

最好使用 Newtonsoft.Json 但您可以更改类以避免自定义序列化程序

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
}

【讨论】:

  • 我不是对您的解决方案投反对票的人,但感谢您对我的两个答案投反对票。我非常感谢你的好意。
  • @PeterCsala 你最好感谢你的“朋友”。
  • 我们怎么知道 OP 使用的是 Text.Json 而不是 JSON.NET?
  • @Evk JsonSerializer.DeserializeSystem.Text.Json 中定义
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-12
  • 2015-10-23
  • 2021-03-31
  • 2018-09-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多