2

I am receiving an HttpResponseMessage that looks like this (data redacted):

{
  "tracks" : {
    "href" : "{href_here}",
    "items" : [ {
      "album" : {
        //stuff here
      },
      "name": "{name here}"
    },
    {
      "album" : {
        //more stuff here
      },
      "name": "{other name here}"
    }
  }
}

My model looks like this:

using System.Text.Json.Serialization;

namespace ProjectName.Models
{
    public class Track
    {
        [JsonPropertyName("album")]
        public Album Album { get; set; }
        
        [JsonPropertyName("name")]
        public string Name { get; set; }
    }
}

I am then attempting to deserialise the response like so:

var response = await _httpClient.GetAsync("URL HERE");

response.EnsureSuccessStatusCode();

return JsonSerializer.Deserialize<IEnumerable<Track>>(await response.Content.ReadAsStringAsync());

I would like to retrieve a list of Tracks (which corresponds to items in JSON).

I cannot find a way online to "skip" parent properties and only deserialize a specific child (in this case items). I do not need href (and the other properties that I have removed).

Is there a way to do this?

Jessica
  • 1,621
  • 2
  • 18
  • 34
  • You using .Net Core 3.x or .Net 5? – dbc Jan 10 '21 at 23:14
  • @dbc .NET Core 3.1 – Jessica Jan 10 '21 at 23:15
  • Too bad, it's easier in .Net 5. Is `"tracks"` a property in the root object? Your JSON sample is missing leading and trailing `{`, `]` and `}` characters so it's hard to tell. – dbc Jan 10 '21 at 23:18
  • Ah that sucks :/ I would migrate but I had a hard time with a previous project and wouldn't like to go down that route right now. Yep, it's all the root object. I'll update the question, good point. – Jessica Jan 10 '21 at 23:20
  • try using [Automapper](https://docs.automapper.org/en/stable/Mapping-inheritance.html) – AussieJoe Jan 10 '21 at 23:20
  • 1
    Honestly in .Net 3.1 it's going to be easiest to create wrapper DTOs for the root and tracks objects. Second easiest is to load into a `JsonDocument` and query it -- but `JsonDocument` doesn't support JSONPath so there's a little work involved. Most effort would be to write code to manually stream through the JSON tokens until you get what you want using `Utf8JsonReader`. More trouble that it's worth really. – dbc Jan 10 '21 at 23:23
  • 2
    Why not just create a class, that represents the root object?That would be the most consistent way. You can omit properties you don't need. – derpirscher Jan 10 '21 at 23:23
  • @AussieJoe I'd rather not install a new package for one small feature. Thanks for the suggestion, though. – Jessica Jan 10 '21 at 23:24
  • @dbc yep, probably the easiest option. I guess that's the way forward. Thanks – Jessica Jan 10 '21 at 23:24
  • BTW you're going to get better performance if you deserialize directly from the `response.Content.ReadAsStreamAsync()`. See [this answer](https://stackoverflow.com/a/58513034/3744182) to [How to deserialize stream to object using System.Text.Json APIs](https://stackoverflow.com/q/58512393/3744182). – dbc Jan 10 '21 at 23:25
  • Why do you want to "skip" this? You can't do that, you need to parse the JSON anyway. – CodeCaster Jan 10 '21 at 23:28
  • @CodeCaster Well, say you have a HTTP response with a bunch of properties, each containing many child properties. Instead of recursively creating objects for each parent, it would be nice to "skip" to the property you need. According to dbc Microsoft have already implemented this in .NET 5. – Jessica Jan 10 '21 at 23:30
  • 1
    I'm not exactly sure what @dbc means, but writing a custom deserializer or brittle XPath is going to cause more work than just generating the classes that correspond to the input JSON and letting your code read `rootObject.Tracks.Items`. The parser will have to read the JSON anyway. See also https://stackoverflow.com/questions/34222588/deserializing-json-into-an-object – CodeCaster Jan 10 '21 at 23:33
  • @dbc, just wondering, how is this possible with .NET 5? – Jessica Jan 12 '21 at 13:48
  • 1
    @Jessica - in .NET 5 you can deserialize to an anonymous type, as shown in [Deserialize anonymous type with System.Text.Json](https://stackoverflow.com/a/65433372/3744182). So you could do `(await JsonSerializerExtensions.DeserializeAnonymousTypeAsync(await response.Content.ReadAsStreamAsync(), new { tracks = new { items = default(IEnumerable) } }))?.tracks?.items;` – dbc Jan 12 '21 at 14:31

1 Answers1

2
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace StackOverflow
{
    public class Album
    {
    }

    public class Item
    {
        [JsonPropertyName("album")]
        public Album Album { get; set; }
        [JsonPropertyName("name")]
        public string Name { get; set; }
    }

    public class Tracks
    {
        [JsonPropertyName("href")]
        public string Href { get; set; }
        [JsonPropertyName("items")]
        public List<Item> Items { get; set; }
    }

    public class Root
    {
        [JsonPropertyName("tracks")]
        public Tracks Tracks { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var jsonStr = "{\"tracks\":{\"href\":\"{href_here}\", \"items\" : [{\"album\" : { }, \"name\": \"{name here}\"}]}}";

            var root = JsonSerializer.Deserialize<Root>(jsonStr);

           //Here is your "IEnumerable<Track>"
            var items = root.Tracks.Items;
        }
    }
}

Your model must have such a structure, then it deserialize your data as expected. Then you can use Linq to bring the result to every format you want.

Ivan Khorin
  • 827
  • 1
  • 5
  • 17