SiBrit
SiBrit

Reputation: 1542

How to avoid needing a root class for a JSON object when using JsonConvert.DeserializeObject

Given this JSON,

{
    "token": {
        "accessToken": "scrciFyGuLAQn6XgKkaBWOxdZA1",
        "issuedAt": "2022-11-06T22:54:27Z",
        "expiresIn": 1799
    }
}

I can get the DeserializeObject to work if I define the model as this

    public class Root
    {
        public Token Token { get; set; }
    }

    public class Token
    {
        public string AccessToken { get; set; }
        public DateTime IssuedAt { get; set; }
        public int ExpiresIn { get; set; }
    }

And use this call:

Root myRoot = JsonConvert.DeserializeObject<Root>(apiResponse);

The third-party API I am calling has all methods returning a similar JSON response, in that it has a header object containing a single object of a specific type, such as:

{
  "user": {
    "preferences": {},
    "address": {},
    "name": {},
    "email": "string",
    "segmentName": "string"
  }
}

which requires a model looking like this:

    public class Address
    {
    }

    public class Name
    {
    }

    public class Preferences
    {
    }

    public class Root
    {
        public User user { get; set; }
    }

    public class User
    {
        public Preferences preferences { get; set; }
        public Address address { get; set; }
        public Name name { get; set; }
        public string email { get; set; }
        public string segmentName { get; set; }
    }

I do not want to be having to define a different Root class for every one of the JSON responses. Is there a way to avoid this?

EDIT 14/11.

Another JSON response looks like this:

{
  "provider": {
    "TOTAL": {
      "count": 0
    }
  }
}

Again, it's an "empty" root object containing the specific object I need.

As zaitsman indicated in his comment, by typing the DeserializeObject call to use a <Dictionary, T>, where T is the actual object I'm after (such as Token or User or Provider), it gets around the need for a root object.

EDIT 15/11.

Just one more example;

{
    "provider": [
        {
            "accountType": [],
            "loginHelp": "string",
            "baseUrl": "string",
            "loginUrl": "string",
            "name": "string",
            "id": 0,
            "lastModified": "string",
            "status": "Supported"
        }
    ]
}

In this case, the C# code to deserialize looks like this:

providers = JsonConvert.DeserializeObject<Dictionary<string, List<Provider>>>(apiResponse)["provider"];

Using Dictionary<string,T> removes the need to define a root class, and T has to be defined as a List<T> since the content is an array.

Upvotes: 1

Views: 1032

Answers (3)

Guru Stron
Guru Stron

Reputation: 143363

I would argue that defining Root objects is better approach because it makes expected JSON structure defined explicitly but if you want you can use Newtonsoft's LINQ API via JObject (docs):

var jObject = JObject.Parse(json);
var user = jObject["user"].ToObject<User>();

Similar can be done via JsonNode API for System.Text.Json (available starting .NET 6).

Upvotes: 1

zaitsman
zaitsman

Reputation: 9519

There are a number of ways you can achieve this.

One of them is:

JsonConvert.DeserializeObject<Dictionary<string, User>>()["user"] 

This will give you a Dictionary<string, User> so you can use any other type as appropriate.

Upvotes: 1

Thorberg
Thorberg

Reputation: 103

One way to solve it by having a wrapper defining all possible "content" names as properties. Then having one property to return the value which is populated after de-serialization.

public class Response<T>
{
   public User User { get; set; }
   public Token Token { get; set; }
   public Token AuthToken { get; set; }
   public XYZ XYZ { get; set; }

   public T Content => GetType().GetProperties().Where(p => p.Name != nameof(Content)).Select(p => p.GetValue(this)).Single(v => v != null) as T;
}

Then, just having a method for deserializing like this

public T DeserializeResponse<T>(string json)
{
  return Deserialize<Response<T>>(json).Content;
}

There is also a more dynamic way possible, if there are too many different contents you need to retrieve, but that would require overriding some more reflection and going deeper into deserialization process.

Upvotes: 0

Related Questions