C# 反序列化 JSON Schema.org 来自网络的食谱

Posted

技术标签:

【中文标题】C# 反序列化 JSON Schema.org 来自网络的食谱【英文标题】:C# Deserialize JSON Schema.org Recipe from web 【发布时间】: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);

【问题讨论】:

能否提供json文件的内容? 一种可能性是配方被一个额外的对象包裹。 "Recipe" : .... 抱歉没有看到您的评论 - 这是文件drive.google.com/file/d/1wS-CnbPjyi6N77WB0TxEVrUsTQypR2q3/… 【参考方案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 LINQ或Querying 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 文件的内容吗?我可以试试这里写代码看看是什么问题

以上是关于C# 反序列化 JSON Schema.org 来自网络的食谱的主要内容,如果未能解决你的问题,请参考以下文章

将 json 反序列化到我的 C# 对象中会出错

在C#中,Json的序列化和反序列化的几种方式总结

jQuery/ASMX:在 C# 中反序列化 JSON 时出错

在C#中,Json的序列化和反序列化的几种方式总结

Json反序列化器或正则表达式或Json解析以在c#中转换Json字符串

如何在 C# .NET 中反序列化复杂的 JSON 对象?