简体   繁体   中英

How to deserialize a property of a child JSON object to a property in the parent model using System.Text.Json?

First off, I had a hard time with the title of this one; I'll edit it if I get any better suggestions.

I have a json string that looks like this

{
   "chapters": [
      {
         "id": 0,
         "tags": {
            "title": "Chapter 1"
         }
      },
      {
         "id": 1,
         "tags": {
            "title": "Chapter 2"
         }
      }
   ]
}

My model for this is as such

public class Chapter
{
   [JsonPropertyName("id")]
   public int Id { get; set; }

   [JsonPropertyName("tags")]
   public ChapterTags Tags { get; set; }

   [JsonIgnore]
   public string Title
   {
      get { return Tags.Title; }
      set { if (Tags.Title != value) Tags.Title = value; }
   }
}
 
public class ChapterTags
{
   [JsonPropertyName("title")]
   public string Title { get; set; }
}

and here is the code I'm using to deserialize the json

var jsonTask = GetJsonAsync();

using var jsonResults = JsonDocument.Parse(await jsonTask);

var jsonChapters = jsonResults.RootElement.GetProperty("chapters");

List<Chapter> chapters = JsonSerializer.Deserialize<List<Chapter>>(jsonChapters) ?? new();

I want to get rid of the Tags property and the ChapterTags class and just be left with

public class Chapter
{
   [JsonPropertyName("id")]
   public int Id { get; set; }

   public string Title {get; set;}
}

as a simple single class model.

My only thought was to use a JsonConverter but could not figure out how to make that work. I can't change the format of the json input because it is being generated by an outside source.

Also, if it matters, I will never have to re-serialize the object back to json.

I am using VS2022 Preview, as that is currently the only way to work with .Net Maui.

try this

List<Chapter> chapters =   JsonDocument.Parse(json)
                           .RootElement.GetProperty("chapters")
                           .EnumerateArray()
                           .Select(c => new Chapter
                           {
                               Id = Convert.ToInt32(c.GetProperty("id").ToString()),
                               Title = c.GetProperty("tags").GetProperty("title").ToString()
                           })
                           .ToList();

I believe this question should help you Custom Deserialization using Json.NET

You are right JsonConverter is something needed here.

I ended up going the converter route.

Recap of input JSON

{
   "chapters": [
      {
         "id": 0,
         "tags": {
            "title": "Chapter 1"
         }
      },
      {
         "id": 1,
         "tags": {
            "title": "Chapter 2"
         }
      }
   ]
}

My converter class

public class JsonChapterTitleConverter : JsonConverter<string>
{
   public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
   {
      using var jsonTags = JsonDocument.ParseValue(ref reader);
      var jsonTitle = jsonTags.RootElement.GetProperty("title");

      return jsonTitle.GetString();
   }

   public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) => throw new NotImplementedException();
}

New model

public class Chapter
{
   [JsonPropertyName("id")]
   public int Id { get; set; }

   [JsonPropertyName("tags")]
   [JsonConverter(typeof(JsonChapterTitleConverter))]
   public string Title { get; set; }
}

The code used to deserialize the JSON is unchanged

I like the answer Serge gave. In this case however, I feel like it violates the Open-Closed Principle (if I understand it correctly). The JSON shown is not the full string; only what is important to me at the moment. If I decide I need to deserialize more of the elements; not only would I have to extend my model, but also modify the deserialization method in my service class. Using the converter means I only have to add another property to my model corresponding to the JSON property I want to capture.

While Maksym's answer isn't a complete answer, the link provided gave me the boost I needed to figure this out.

Thank you.

Comments welcome.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM