4
votes

I have an API JSON response that wraps the data content in a data property, which looks like this:

{ 
   "data":{ 
      "email":"[email protected]",
      "mobile":"+1555555123",
      "id":4,
      "first_name":"Merchant",
      "last_name":"Vendor",
      "role":"Merchant",
   }
}

So when making request for a User Object with a Library like RequestSharp, the response.Content has the content for the User wrapped in the data json property as it comes from the API. Code:

var request = RequestHelper.CreateTokenRequest(email, password); // Create the request from a helper
var client = new RestClient(BaseUrl); // create new RestSharp Client
IRestResponse response = client.Execute(request); // execute the request
var content = response.Content; // raw content as string

This is fine, but when I go to deserialize the json to an Object with System.Text.Json, like the following, will create the User object but will not assign any of the attributes, although this is sort of expected because the serializer is looking for properties with first_name and last_name... not ['data']['first_name']

User account = JsonSerializer.Deserialize<User>(response.Content, options);

How can I get the JsonSerializer.Deserialize to ignore the data wrapper? In other API calls it may be the name of the Object such as transaction or user, either way, it wraps the data.

Other Notes:

I am targeting the latest .Net Core 3.1 and am migrating from Newtonsoft Json.Net


My User Object:

using System.ComponentModel;
using System.Text.Json.Serialization;

namespace MyApplication.Models
{
    public interface IUser
    {
        string FirstName { get; set; }
        string LastName { get; set; }
        string Email { get; set; }
        string Mobile { get; set; }
        string Id { get; set; }
        string Role { get; set; }
    }

    public class User : IUser
    {
        [JsonPropertyName("first_name")]
        public string FirstName { get; set; }

        [JsonPropertyName("last_name")]
        public string LastName { get; set; }

        [JsonPropertyName("email")]
        public string Email { get; set; }

        [JsonPropertyName("mobile")]
        public string Mobile { get; set; }

        [JsonPropertyName("id")]
        public string Id { get; set; }

        [JsonPropertyName("role")]
        public string Role { get; set; }

        [JsonIgnore]
        public string Token { get; set; }
    }
}


Update after resolution:

I selected the answer from u/Nikunj Kakadiya below as something that would work and was most similar to what I ended up doing.

I created a generic template based container class to handle the data like this:

public class Container<T>
{
    [JsonPropertyName("data")]
    public T Data { get; set; }
}

I then used that container class to wrap the returned json contents from the API call, like this:

var options = new JsonSerializerOptions
{
    AllowTrailingCommas = true
};

Container<User> accountContainer = JsonSerializer.Deserialize<Container<User>>(response.Content, options);
User account = accountContainer.Data;

Additionally, as u/Pavel Anikhouski noted, my serialization of the User class led to an error that required me to create a custom converter for the id field. The API returns the id as an integer although it is a string in the User class. This was the error I ran into which was initially confusing but I was able to figure out pretty quickly: ERROR: The JSON value could not be converted to System.String. Path: $.data.id | LineNumber: 0 | BytePositionInLine: 77.

Here is the custom converter IntToStringConverter:

public class IntToStringConverter : JsonConverter<string>
{
    public override string Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options) => reader.GetInt32().ToString();

    public override void Write(
        Utf8JsonWriter writer,
        string value,
        JsonSerializerOptions options) =>
        writer.WriteStringValue(value);
}

and then changed the User Class to use the customer converter:

...
    [JsonPropertyName("id")]
    [JsonConverter(typeof(IntToStringConverter))]
    public string Id { get; set; }
...
4
There is an error Cannot get the value of a token type 'Number' as a string. for id propertyPavel Anikhouski
Do you still need an answer? There are several working answers below. If you really don't care about the root property name you could always deserialize to a dictionary and take the first value, i.e. JsonSerializer.Deserialize<Dictionary<string, User>>(response.Content, options).Values.SingleOrDefault();dbc

4 Answers

5
votes

You need to make one another class. That is given below

Public class UserData
{
    public User data { get; set; }; 
}

Now you can Deserialize data using new class called UserData like below

UserData account = JsonSerializer.Deserialize<UserData>(response.Content, options);
1
votes

It's possible to get a User object using System.Text.Json API without specifying a name of property data from your JSON sample

{ 
   "data":{ 
      "email":"[email protected]",
      "mobile":"+1555555123",
      "id":4,
      "first_name":"Merchant",
      "last_name":"Vendor",
      "role":"Merchant",
   }
}

by the following code

var document = JsonDocument.Parse(json, new JsonDocumentOptions { AllowTrailingCommas = true });
var enumerator = document.RootElement.EnumerateObject();
if (enumerator.MoveNext())
{
    var userJson = enumerator.Current.Value.GetRawText();
    var user = JsonSerializer.Deserialize<User>(userJson,
        new JsonSerializerOptions {AllowTrailingCommas = true});
}

In the sample above the JsonDocument is loaded, than RootElement is enumerated to get the first nested object as text to deserialize into User instance.

It's more easier to get a property by name like document.RootElement.GetProperty("data");, but the name can be different actually, according to your question. Accessing through indexer, like document.RootElement[0] is also not possible, because it works only when ValueKind of an element is Array, not the Object, like in your case

I've also changed the "id":4, to "id":"4",, because getting an error

Cannot get the value of a token type 'Number' as a string.

0
votes

You can create an object like this:

  public class Data
   {
    public string email { get; set; }
    public string mobile { get; set; }
    public int id { get; set; }
    public string first_name { get; set; }
    public string last_name { get; set; }
    public string role { get; set; }
    }

   public class RootObject
   {
    public Data data { get; set; }
   }

then

var data = JsonSerializer.Deserialize<RootObject>(JsonData);

then you can access the data like the following:

RootObject.Data.email ;
RootObject.Data.first_name

Also, anytime you need to convert JSON string to C# POCO class you can use a tool like this : http://json2csharp.com/

-1
votes

Either you get rid of the data object or you can write a custom json Converter. I suggest to change the sent data or just consume it as it is because what you are trying to do is not a best practice.