0

I am new to .NET Core. I am creating an API which returns some data in response. I have created a utility function successResponse to return a generic success response with data to client.

Here is the model class of that successResponse

public class SuccessResponse
{
    public object? payload { get; set; } = null;
    public bool success { get; set; } = false;

    public SuccessResponse(object data, bool isSuccess)
    {
        payload = data;
        success = isSuccess;
    }
}

I have also created a helper class that have a function to return successResponse like this

public static class ResponseHandler
{
    public static SuccessResponse successResponse(object data) 
    {
        return new SuccessResponse(data, true);
    }

    public static ErrorResponse errorResponse(string error)
    {
        return new ErrorResponse(error);
    }
} 

In my controller, I have code like this:

[HttpPost]
public async Task<IActionResult> GetIncomingFile(IFormFile file)
{
    try
    {
        var options = new JsonSerializerOptions { IncludeFields = true };

        List<ImportedFileData> importedExcelFileData = await ExcelMapperFileReader.getFileData(file);
        BalanceSheet balanceSheetData = BalanceSheetReport.createBalanceSheet(importedExcelFileData);

        return Ok(ResponseHandler.successResponse(JsonSerializer.Serialize(balanceSheetData, options)));
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        return BadRequest(ResponseHandler.errorResponse(ex.Message));
    }
}

My model class BalanceSheet is like this

public class BalanceSheet
{
    public BalanceSheetAssets assets = null!;
    public BalanceSheetLiabilities liabilities = null!;
    public BalanceSheetEquity equity = null!;

    public BalanceSheet(BalanceSheetAssets incomingAssets, BalanceSheetLiabilities incomingLiabilities, BalanceSheetEquity incomingEquity)  
    {
        assets = incomingAssets;
        liabilities = incomingLiabilities;
        equity = incomingEquity;
    }
}

The problem is that I get this response on the client:

{
    "payload": "{\"assets\":{\"currentAssets\":[{\"title\":\"Inventory\",\"amount\":85300,\"code\":\"CA\"},{\"title\":\"Accounts Receivable\",\"amount\":4700,\"code\":\"CA\"},{\"title\":\"Cash\",\"amount\":5000,\"code\":\"CA\"}],\"nonCurrentAssets\":[{\"title\":\"Furniture\",\"amount\":200000,\"code\":\"NCA\"}],\"totalAssets\":255000},\"laibilities\":{\"currentLiabilities\":[{\"title\":\"Inventory\",\"amount\":85300,\"code\":\"CA\"},{\"title\":\"Accounts Receivable\",\"amount\":4700,\"code\":\"CA\"},{\"title\":\"Cash\",\"amount\":5000,\"code\":\"CA\"}],\"nonCurrentLiabilities\":[{\"title\":\"Furniture\",\"amount\":200000,\"code\":\"NCA\"}],\"totalLiabilities\":45000},\"equity\":{\"equityList\":[{\"title\":\"Equity\",\"amount\":150000,\"code\":\"EQ\"},{\"title\":\"Retained Earnings\",\"amount\":60000,\"code\":\"EQ\"}],\"totalEquity\":210000}}",
    "success": true
}

But I want to receive correctly formatted JSON response at the client.

How can I do that?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Fahad Subzwari
  • 2,109
  • 3
  • 24
  • 52
  • Is that the actual returned json? It's not formatted correctly. – mxmissile Dec 15 '22 at 18:08
  • Quite sure there is a typo in your question. Is the client receiving an empty object ({}) for the payload property, right ? – Enrico Massone Dec 15 '22 at 18:16
  • Have you already tried to JSON serialize the object you get from `ResponseHandler.succesResponse(balanceSheetData)`? You can do that manually. See [here](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/how-to?pivots=dotnet-7-0#how-to-write-net-objects-as-json-serialize). You can try to do that one line before returning and try to debug to see the output string – Enrico Massone Dec 15 '22 at 18:19
  • Baiscally the `OkObjectResult` will do a JSON serialization of the provided `object`, by using `System.Text.Json` library. Try to manually do the serialization and verify the string you obtain by doing that. Just to confirm that it is exactly the same string you see on the client. – Enrico Massone Dec 15 '22 at 18:22
  • @EnricoMassone yes client is receiving an empty object for `payload` property – Fahad Subzwari Dec 15 '22 at 18:22
  • @EnricoMassone when i try to `Serialize` my data like this `string responseString = JsonSerializer.Serialize(balanceSheetData);` so the output saved in `responseString` is `"{}"` – Fahad Subzwari Dec 15 '22 at 18:30
  • Then you need to show us the code of BalanceSheet – Evk Dec 15 '22 at 18:31
  • @Evk i have added the code of `BalanceSheet`. Please read the post again – Fahad Subzwari Dec 15 '22 at 18:34
  • @Evk i have fixed the issue of JSON serialization. But now the problem is that client is receiving the resposne in json string with lots of `\` in it. How can i receive data in json in response? – Fahad Subzwari Dec 15 '22 at 18:38
  • @Evk i have updated my post. Please read it again – Fahad Subzwari Dec 15 '22 at 18:44
  • 1
    Why are you serializing the payload yourself? Let the framework handle that. Return an object with the desired shape, the framework will serialize it for you. – mason Dec 15 '22 at 18:48
  • @mason so how can i return data? can you please give an example by using my code? – Fahad Subzwari Dec 15 '22 at 18:50
  • ASP.NET Core will serialize the response to JSON. The classes `SuccessResponse` and `ErrorResponse` means nothing to ASP.NET, in fact its' a bad idea to return an HTTP status OK only to include a failure in the payload. As far as ASP.NET is concerned, that's just an object that will get serialized to JSON – Panagiotis Kanavos Dec 15 '22 at 18:51
  • Don't serialize the balanceSheetData yourself. Let the framework do it. It's that simple. Remove the call to JsonSerializer.Serialize. Since you have been manually serializing the payload into a strong, then the framework serializes the entire SuccessResponse, it means the payload ends up being double encoded. So the fix is easy: let the framework handle the serialization to JSON. Though Panagiotis comments on the design are definitely great to take into consideration as further improvement. – mason Dec 15 '22 at 18:52
  • @mason when i removed the `JsonSerializer` and return like this `return Ok(ResponseHandler.succesResponse(balanceSheetData));` then it sends this response on client as json `{payload: {}, success: true}` – Fahad Subzwari Dec 15 '22 at 18:53
  • @FahadSubzwari the best option is to just `return Ok(balanceSheetData)`. The `Response` classes are actively harmful because they're lying to the client in case of error. In case of exception, you can just rethrow the exception and ASP.NET Core will convert it to a correct 500 response. `BadRequest` is only meant for truly invalid request objects, eg those that fail validation. There's a proper standard-based ProblemDetails response in that case too. – Panagiotis Kanavos Dec 15 '22 at 18:54
  • Use https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-6.0#optionsbuilder-api. Or don't use public fields, it's kind of bad practice in .NET. – Evk Dec 15 '22 at 18:54
  • @PanagiotisKanavos when i did this `return Ok(balanceSheetData)` so i received this as response `{}` – Fahad Subzwari Dec 15 '22 at 18:56
  • In your code, a really Bad Request would mean that the Excel file is somehow bad. In that case you should *log* the actual exception and return a `BadRequest` response that says that the Excel file is bad, not include the exception message. That won't help the clients understand what's going on – Panagiotis Kanavos Dec 15 '22 at 18:56
  • @FahadSubzwari `so i received this as response {}` that means `balanceSheetData` is empty. Which *it is* - fields are just implementation details, they aren't included during serialization – Panagiotis Kanavos Dec 15 '22 at 18:57
  • @PanagiotisKanavos no, it has data. On debugging, on line `BalanceSheet balanceSheetData = BalanceSheetReport.createBalanceSheet(importedExcelFileData);` it shows that `balanceSheetData` has all data – Fahad Subzwari Dec 15 '22 at 18:58
  • The very fact you had to use `IncludeFields = true` says you already found that fields aren't serialized by tried to bypass this instead of fixing the bug. Only properties are part of an object's API, they aren't just getters and setters. Fields, even public fields, are just implementation details. – Panagiotis Kanavos Dec 15 '22 at 18:59
  • @FahadSubzwari `no, it has data.` it may have internal data but it doesn't have any public, serializable properties. Don't use public fields. That's how .NET always worked and a *major* improvement over C++ and Java – Panagiotis Kanavos Dec 15 '22 at 19:00
  • @PanagiotisKanavos thank you so much for your guidance. I got the idea. Basically my class `BalanceSheet` should be a `getter setter` `class` to have the actual data. Issue is resolved. I will post the solution for others guidance – Fahad Subzwari Dec 15 '22 at 19:08

1 Answers1

0

You are getting an empty object because System.Text.Json, by default, ignores fields as documented here.

The BalanceSheet class is designed to have public fields, so you get an empty object in the response.

You have a couple of options:

  1. you can instruct System.Text.Json to include fields by adding the JsonIncludeAttribute to all the public fields of the BalanceSheet class. See here for more details.
  2. you can chage the design of the BalanceSheet class and use properties instead of fields.

In options 1 you need to change the code this way:

public class BalanceSheet
{
  [JsonInclude]
  public BalanceSheetAssets assets = null!;
  [JsonInclude]
  public BalanceSheetLiabilities laibilities = null!;
  [JsonInclude]
  public BalanceSheetEquity equity = null!;

  public BalanceSheet(
   BalanceSheetAssets incomingAssets, 
   BalanceSheetLiabilities incomingLaibilities, 
   BalanceSheetEquity incomingEquity) {
    assets = incomingAssets;
    laibilities = incomingLaibilities;
    equity = incomingEquity;
  }
}

In options 2 you need to change the code this way:

public class BalanceSheet
{
  public BalanceSheetAssets Assets { get; } = null!;
  public BalanceSheetLiabilities Laibilities { get; } = null!;
  public BalanceSheetEquity Equity { get; } = null!;

  public BalanceSheet(
   BalanceSheetAssets incomingAssets, 
   BalanceSheetLiabilities incomingLaibilities, 
   BalanceSheetEquity incomingEquity) {
    Assets = incomingAssets;
    Laibilities = incomingLaibilities;
    Equity = incomingEquity;
  }
}

In general using public fields is a bad design, so I would go with option 2. See here for more details.

Either way you need to let ASP.NET core to do the JSON serialization for you, so change the return statement in the action method this way:

return Ok(ResponseHandler.succesResponse(balanceSheetData));

In my comment I suggested you to manually serialize to JSON just as a debugging step, to help us understanding the nature of your issue. You should never manually serialize the response object to JSON: this is done automatically for you by the ASP.NET core framework, when the OkObjectResult is executed.

Enrico Massone
  • 6,464
  • 1
  • 28
  • 56
  • Thanks, i already got the answer. And yes, you are right. I didn't know these details. Thanks for the great explanation. I changed my design and implemented option 2 of having properties instead of public fields. You answer also helped me alot. – Fahad Subzwari Dec 16 '22 at 07:43