Search code examples
c#jsonhttpclient

HttpClient ObjectContent JSON: format with root class name in json object?


I generate a C# class for a json source using json2csharp.com. My json is:

{
  "email_verified": true,
  "user_id": "gg2323",
  "app_metadata": {
    "tc_app_user": {
      "user_guid": "c0fb150f6er344df98ea3a06114e1e4a",
      "cto_admin_a_user_id": "551294d4f6cfb46e65a5aq71",
      "lang": "EN",
      "country": "USA",
      "disabled": false
  }

and my resulting C# is:

public class TcAppUser
{
    public string user_guid { get; set; }
    public string cto_admin_a_user_id { get; set; }
    public string lang { get; set; }
    public string country { get; set; }
    public bool disabled { get; set; }
}

public class AppMetadata
{
    public TcAppUser tc_app_user { get; set; }
    public int logins_count { get; set; }
}

public class RootObject
{
    public bool email_verified { get; set; }
    public string user_id { get; set; }
    public AppMetadata app_metadata { get; set; }
}

Using the .NET HttpClient GET, I can read into this C# structure from the JSON API quite nicely. Going the other way (POST, PATCH) poses a problem: my app_metadata property name is dropped in the generated JSON output when I use a common approach like:

    //Would be nice: var contentIn = new ObjectContent<string>(RootObjectInstance.app_metadata, new JsonMediaTypeFormatter());  
string json = JsonConvert.SerializeObject(RootObjectInstance.app_metadata);         
HttpResponseMessage response = await hclient.PatchAsync("api/users/" + user_id, new StringContent(json, Encoding.UTF8, "application/json"));

The resulting JSON is now:

{
    "tc_app_user": {
      "lang": "en-en",
      "country": "GER",
      "disabled": false
    }
}

My quick hack is to use the following additional wrapper to dynamically repackage the app_metadata property so it has the same format going out that it had coming in. The rest remains the same as above:

dynamic wireFormatFix = new ExpandoObject();
                wireFormatFix.app_metadata = usr.app_metadata;
                string json = JsonConvert.SerializeObject(wireFormatFix);

Now my JSON output corresponds to the JSON input. My question: what is best-practice to achieve symmetric json input and output here without a pesky format fix?

EDIT: If I try to PATCH the entire structure (RootObjectInstance instead of RootObjecInstance.app_metadata) I get:

{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Payload validation error: 'Additional properties not allowed: 'user_id'."
}

So, I must either send the app_metadata subset/property of the C# RootObject, properly packaged, or I must selectively delete fields from the RootObject to meet the API's requirements.

Thanks!


Solution

  • The root app_metadata tag is being removed from your JSON because you're simply not serializing it. This:

    string json = JsonConvert.SerializeObject(RootObjectInstance.app_metadata);
    

    Will serialize everything that is inside app_metadata.

    If you serialized the entire object graph, you wouldn't need to patch anything:

    string json = JsonConvert.SerializeObject(RootObjectInstance);
    

    As a side note, you should follow C# naming conventions. You can use JsonProperty to help you with that.

    Edit:

    Ok, after your edit i see the actual problem. You're calling an API by user_id in your query string, and you also have a user_id property inside your object. This seems like you need two different objects for the job.

    You have a couple of possibilities:

    1. Create an object hierarchy:

      public class BaseObject
      {
          [JsonProperty(email_verified)]
          public bool EmailVerified { get; set; }
          [JsonProperty(app_metadata)]
          public AppMetadata AppMetadata { get; set; }
      }
      
      public class ExtendedObject : BaseObject
      {
         [JsonProperty(user_id)]
         public string UserId { get; set; }
      }
      

    And then use the base type to serialize the data:

    var baseObj = new BaseObject(); // Fill the object properties.
    var json = JsonConvert.SerializeObject(intermidiateObj);
    HttpResponseMessage response = await hclient.PatchAsync("api/users/" + 
                                                            user_id, 
                                                            new StringContent(json,
                                                            Encoding.UTF8,
                                                            "application/json"));
    
    1. Use an anonymous object which includes only properties you actually need:

      var intermidiateObj = new { app_metadata = usr.app_metadata };
      var json = JsonConvert.SerializeObject(intermidiateObj);
      HttpResponseMessage response = await hclient.PatchAsync("api/users/" + 
                                                          user_id, 
                                                          new StringContent(json,
                                                          Encoding.UTF8,
                                                          "application/json"));