【问题标题】:JSON.Net Serializing Derived ClassesJSON.Net 序列化派生类
【发布时间】:2015-08-18 01:05:37
【问题描述】:

为了使用我正在开发的食谱网络服务,我有以下类来保存和序列化食谱数据:

class Recipe {

    public string RecipeId { get; private set; }

    public string UserId { get; set; }
    public string Title { get; set; }

    public IList<string> IngredientsList { get; set; }

    public List<Group<string, Ingredient>> IngredientsWithHeaders { get; set; }
    public List<Group<string, string>> InstructionsWithHeaders { get; set; }

    public List<string> Notes { get; set; }
    public ISet<string> Tags { get; set; }
    public int Yield { get; set; }

    public Recipe(string recipeId)
    {
        RecipeId = recipeId;
        IngredientsWithHeaders = new List<Group<string,Ingredient>>();
        InstructionsWithHeaders = new List<Group<string, string>>();
        IngredientsList = new List<string>();
    }

    public byte[] Image { get; set; }
}

class Ingredient
{
    public string Quantity { get; set; }
    public string Modifier { get; set; }
    public string Unit { get; set; }
    public string IngredientName { get; set; }
    public string Preparation { get; set; }

    public Ingredient(string[] line)
    {
        if (!string.IsNullOrWhiteSpace(line.ElementAt(0)))
        {
            Quantity = line.ElementAt(0);
        }
        if (!string.IsNullOrWhiteSpace(line.ElementAt(1)))
        {
            Unit = line.ElementAt(1);
        }
        if (!string.IsNullOrWhiteSpace(line.ElementAt(2)))
        {
            IngredientName = line.ElementAt(2);
        }
        if(line.Length>3)
        {
            Preparation = line.Last();
        }
    }
}

class Group<K, T> : ObservableCollection<T>
{
    public K Key { get; set; }

    public Group(K key, IEnumerable<T> items) : base(items)
    {
        Key = key;
        Debug.WriteLine(key);
    }
}    

我为 List&lt;Group&lt;string, Ingredient&gt;&gt; 得到的 JSON 输出是

{
"IngredientsWithHeaders": [
    [
        {
            "Quantity": "3",
            "Modifier": null,
            "Unit": "tbsp",
            "IngredientName": "butter",
            "Preparation": null
        },
        {
            "Quantity": "1",
            "Modifier": null,
            "Unit": "16 oz. bag",
            "IngredientName": "marshmallows",
            "Preparation": null
        },
        {
            "Quantity": "2/3",
            "Modifier": null,
            "Unit": "cup",
            "IngredientName": "dry cake mix",
            "Preparation": null
        },
        {
            "Quantity": "6",
            "Modifier": null,
            "Unit": "cups",
            "IngredientName": "crispy rice cereal",
            "Preparation": null
        },
        {
            "Quantity": "1",
            "Modifier": null,
            "Unit": "container",
            "IngredientName": "sprinkles",
            "Preparation": "optional"
        }
        ]
    ]
}

而我想要得到的更像是

{
"IngredientsWithHeaders": [
    {
        "Group": {
            "Header": "BlankHeader",
            "Items": [
                {
                    "Quantity": "3",
                    "Modifier": null,
                    "Unit": "tbsp",
                    "IngredientName": "butter",
                    "Preparation": null
                },
                {
                    "Quantity": "1",
                    "Modifier": null,
                    "Unit": "16 oz. bag",
                    "IngredientName": "marshmallows",
                    "Preparation": null
                },
                {
                    "Quantity": "2/3",
                    "Modifier": null,
                    "Unit": "cup",
                    "IngredientName": "dry cake mix",
                    "Preparation": null
                },
                {
                    "Quantity": "6",
                    "Modifier": null,
                    "Unit": "cups",
                    "IngredientName": "crispy rice cereal",
                    "Preparation": null
                },
                {
                    "Quantity": "1",
                    "Modifier": null,
                    "Unit": "container",
                    "IngredientName": "sprinkles",
                    "Preparation": "optional"
                }
                ]
            }
        }
    ]
}

我需要编写自定义序列化程序吗?如果是这样,我如何在不知道它是否是

的情况下将对象转换为参数化组
Group<string, Ingredient> 

Group<string, string>

?

【问题讨论】:

  • 您要的 JSON 无效。尝试将其上传到jsonlint.com,您将收到错误消息。 JSON 容器可以是对象(一组无序的名称/值对)或数组——但不能同时是两者。根据jsonlint.com,您能否举一个您想要的有效 JSON 示例?
  • 我的错误,即将使用更具体的 JSON 进行编辑。

标签: c# json serialization json.net


【解决方案1】:

这里的问题是您的Group&lt;K, T&gt; 是一个也有属性的集合。由于 JSON 容器可以是数组(没有属性)或对象(具有命名的键/值对),因此无法在不丢失数据的情况下自动映射具有自定义属性的集合。 Json.NET(和所有其他序列化程序 AFAIK)选择映射项目而不是自定义属性。

你有几种方法可以解决这个问题:

  1. 编写您自己的自定义JsonConverter。您可以按照Json.Net returns Empty Brackets 的方式使用反射来确定通用参数。

  2. [JsonObject]标记你的Group&lt;K, T&gt;

第二个选项似乎最简单,看起来像:

[JsonObject(MemberSerialization = MemberSerialization.OptIn)] // OptIn to omit the properties of the base class,  e.g. Count
class Group<K, T> : ObservableCollection<T>
{
    [JsonProperty("Header")]
    public K Key { get; set; }

    [JsonProperty("Items")]
    IEnumerable<T> Values
    {
        get
        {
            foreach (var item in this)
                yield return item;
        }
        set
        {
            if (value != null)
                foreach (var item in value)
                    Add(item);
        }
    }

    public Group(K Header, IEnumerable<T> Items) // Since there is no default constructor, argument names should match JSON property names.
        : base(Items)
    {
        Key = Header;
    }
}

顺便说一句,你还有另一个问题——你的Ingredient 类没有默认构造函数,如果line 参数为空,它的单个参数化会抛出NullReferenceException。在没有默认构造函数的情况下,Json.NET 将调用单个参数化构造函数,按名称将 JSON 对象值映射到构造函数参数。因此,反序列化会引发异常。

你有几种方法可以解决这个问题:

  1. 添加公共默认构造函数。

  2. 添加一个private默认构造函数并用[JsonConstructor]标记:

    [JsonConstructor]
    Ingredient() { }
    
  3. 添加一个 private 默认构造函数并使用 ConstructorHandling.AllowNonPublicDefaultConstructor 反序列化:

    var settings = new JsonSerializerSettings { ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor };
    var recipe = JsonConvert.DeserializeObject<Recipe>(json, settings);
    
  4. 在构造函数中添加if (line != null) 检查。 (不太推荐。相反,您的构造函数应该显式抛出 ArgumentNullException。)

完成此操作后,您将得到如下所示的 gt JSON:

{
  "IngredientsWithHeaders": [
    {
      "Header": "BlankHeader",
      "Items": [
        {
          "Quantity": "3",
          "Modifier": null,
          "Unit": "tbsp",
          "IngredientName": "butter",
          "Preparation": null
        }
      ]
    }
  ],
}

您建议的 JSON 具有额外的嵌套级别

{
"IngredientsWithHeaders": [
    {
        "Group": {
            "Header": "BlankHeader",

这个额外的"Group" 对象是不必要的。

【讨论】:

  • 我刚刚实施了这些更改,并立即看到了不同。感谢您解决额外级别的嵌套问题 - 我知道我想要的示例不是很理想,但我不确定它应该是什么样子。
猜你喜欢
  • 1970-01-01
  • 2014-03-31
  • 2017-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-10
相关资源
最近更新 更多