【问题标题】:C# Deserialize JSON Schema.org Recipe from webC# 反序列化 JSON Schema.org 来自网络的食谱
【发布时间】:2021-07-09 02:55:26
【问题描述】:

我正在尝试用 C# 编写一个食谱解析器。我尝试过同时使用 Newtonsoft 和 System.Text.Json。我使用了在线 json 类构建器和 VS2019 Paste 作为 json 类。我已经运行了 Newtonsoft 示例代码。

Schema.org 食谱https://schema.org/Recipe

我正在使用的课程。我真的只对这两个值感兴趣

public class Recipe
{
public string recipeIngredient { get; set; }
public string recipeInstructions { get; set; }
}

代码 - 此代码运行时没有错误。

var document = File.ReadAllText(@"D:\recipeExample.json");
Recipe recipe = null;
try
{
    recipe = System.Text.Json.JsonSerializer.Deserialize<Recipe>(document);
}
    catch (System.Text.Json.JsonException ex)
{
    Console.WriteLine($"Error parsing : {ex}");
}

试图读取值。没有错误或价值。

Console.WriteLine(recipe.recipeIngredient);

我不确定我是否了解如何遍历 schema.org 文档。在生成的类中,我可以看到 Graph 和 Context 我猜是根节点。但是我的实验表明我不需要所有的类——只需要 recipeIngredient 和 recipeInstructions。 这只是一个食谱 - 我想解析一个食谱列表,我相信它们都会有自己的类,所以我正在寻找最通用的方法来获取我正在寻找的 2 个字段值。

编辑

使用 tymtam 的 Json Document 示例,我可以看到那里有一个对象

Console.WriteLine(recipes.Count);

但我无法获取要显示的值

Console.WriteLine(recipes[0].Ingredients.ToString());
Console.WriteLine(recipes[0].Instructions.ToString());

我也尝试用

打印成分
foreach (var recipe in recipes)
{
Console.WriteLine(recipes[0].Ingredients);
}

但这只会打印对象名称而不是元素

System.Linq.Enumerable+WhereSelectEnumerableIterator

编辑 2:已解决

foreach (var ingredient in recipes[0].Ingredients)
   Console.WriteLine(ingredient);

foreach (var instruction in recipes[0].Instructions)
   Console.WriteLine(instruction);

【问题讨论】:

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


【解决方案1】:

json的相关部分如下:

{
   "@context":"https://schema.org",
   "@graph":[
      (...)
      {
         "@type":"Recipe",
         "recipeIngredient":[
            "12 oz rice noodles",
            (...)
         ],
         "recipeInstructions":[
            {
               "@type":"HowToStep",
               "text":"Bring ...",
               "name":"Bring ...",
               "url":"https://..."
            },
            (...)
         ],
  (...)

System.Text.Json 和 JsonDocument

using (JsonDocument doc = JsonDocument.Parse(json))
{
    JsonElement root = doc.RootElement;
    JsonElement graph = root.GetProperty("@graph");

    var recipes = graph.EnumerateArray()
    .Where(g => g.GetProperty("@type").GetString() == "Recipe")
    .Select(r => new
    {
        Ingredients = r.GetProperty("recipeIngredient").EnumerateArray().Select(p => p.GetString()),
        Instructions = r.GetProperty("recipeInstructions").EnumerateArray().Select(p => new
        {
            @type = p.GetProperty("@type").GetString(),
            text = p.GetProperty("text").GetString(),
            name = p.GetProperty("name").GetString(),
            url = p.GetProperty("url").GetString(),
        })
    })
    .ToList();
}

System.Text.Json 和类

class Root 
{
    [JsonPropertyName("@context")]
    public string Context {get; set;}
    
    [JsonPropertyName("@graph")]
    public List<Element> Graph {get; set;}
}


class Element {
    [JsonPropertyName("@type")]
    public string Type {get;set;}
    public List<string> RecipeIngredient {get; set;}

    public List<Instruction> RecipeInstructions {get; set;}
}

class Instruction {
    public string Text {get; set;}
}
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
var x = JsonSerializer.Deserialize<Root>(json, options);
var recipes = x.Graph.Where( o => o.Type == "Recipe");

Json.Net

请看官方Querying JSON with LINQQuerying JSON with SelectToken

这是一个带有查询和类的混合搭配解决方案:

using Newtonsoft.Json.Linq;

JObject o = JObject.Parse(json);
JToken t = o.SelectToken("$.@graph[?(@.@type == 'Recipe')]");
var recipe = t.ToObject<Recipe>();

(...)
class Recipe {
    public List<string> RecipeIngredient {get; set;}

    public List<Instruction> RecipeInstructions {get; set;}
}

class Instruction {
    public string Text {get; set;}
}

对于多个配方,请使用 SelectTokens

【讨论】:

  • 谢谢@tymtam 你能告诉我如何枚举列表吗?我已经运行了前两个示例而没有错误,但无法确定将列表打印到控制台的内容/方式。
  • 你可以序列化回来:Console.WriteLine(JsonSerializer.Serialize(recipes, new JsonSerializerOptions {WriteIndented = true}));
【解决方案2】:

我认为您的问题是您正在一次读取整个文件。因此反序列化方法可能无法分配单个值,因为您试图从所有文本中解析一个变量

如果您使用 System.Text.Json 你应该可以在你的类中设置 2 个属性,

public string recipeIngredient {get; set;}
public string recipeInstructions {get; set;}
public override string ToString() => JsonSerializer.Serialize<Recipe>(this);

然后当你反序列化时你可以做这样的事情

public list<Recipe> getRecipe()
{
   using (var jsonFileReader = File.OpenText(filename))
   {
      return JsonSerializer.Deserialize<Recipe>(jsonFileReader.ReadToEnd());
   }
}

有了这个,您应该能够创建一个类型为您的食谱类的列表。然后您应该可以参考该列表。 (编辑) 我忘了提到它,但你可能想调用它,这样做你会说 list&lt;Recipe&gt; recipeList = getRecipe(); 然后您只需遍历列表,每个对象都会有一个“.recipeIngredient”和“.recipeInstructions”,您可以使用它们来格式化输出

【讨论】:

  • 感谢您的回答,但我不明白您的建议。我复制了你的代码 List 应该是大写的 L 吗?然后得到错误:无法将类型'WindowsFormsApp1.Form1.Recipe'隐式转换为'System.Collections.Generic.List
  • 我建议创建一个不绑定到表单的新类,并使用两个字段作为您的 Recipe 类。您的问题似乎是 Recipe 类正在引用一个表单。
  • "一个不绑定到表单的新类" - 它是一个 WinForms 应用程序。我在 App 中创建了一个新的 Recipe 类。这是你的意思吗?不知道我应该把 list getRecipe() 放在哪里 - 我在表单和课堂上仍然遇到同样的错误。我还添加了 getRecipe(string filename)
  • 我假设它说“无法隐式转换类型'WindowsFormsApp1.Form1.Recipe'”的原因是因为覆盖方法在告诉方法它的序列化内容时使用了“this”。但在你的情况下,它的寻址似乎是指一种形式的“this”实例。也许我误解了你要做什么
  • 您介意链接 json 文件的内容吗?我可以试试这里写代码看看是什么问题
猜你喜欢
  • 1970-01-01
  • 2014-01-09
  • 2015-05-01
  • 2014-07-19
  • 1970-01-01
  • 1970-01-01
  • 2018-02-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多