Dart - Converting ResponseStatus from Json is NULL

I am testing and debugging my own Error handling in a Flutter application. On the server I filled a ResponseStatus object with some fake data and returned it. In C# it looks as follows:

                //TODO for testing only, remove later....
                response.ResponseStatus = new ResponseStatus
                {
                    Message = "Warning, some files where not found!",
                    Meta = new Dictionary<string, string>()
                };
                response.ResponseStatus.Message = "This is a test message";
                response.ResponseStatus.ErrorCode = "500";
                response.CreditCard = null;
                
                return response;

In Dart the ResponseStatus object inside my Response object is always null while the json Map contains the correct data as you can see in the below screenshot from the debugger in AndroidStudio.

So I tried to debug it. In a file called client_dtos.dart I found the class ResponseStatus:

class ResponseStatus implements IConvertible {
  String? errorCode;
  String? message;
  String? stackTrace;
  List<ResponseError>? errors;
  Map<String, String?>? meta;

  ResponseStatus({this.errorCode, this.message, this.stackTrace, this.errors, this.meta});

  ResponseStatus.fromJson(Map<String, dynamic> json) {
    fromMap(json);
  }

  fromMap(Map<String, dynamic> json) {
    errorCode = JsonConverters.getJson(json, 'errorCode');
    message = JsonConverters.getJson(json, 'message');
    stackTrace = JsonConverters.getJson(json, 'stackTrace');
    errors = JsonConverters.fromJson(JsonConverters.getJson(json, 'errors'), 'List<ResponseError>', context!);
    meta = JsonConverters.toStringMap(JsonConverters.getJson(json, 'meta'));
  }

  Map<String, dynamic> toJson() => {
        'errorCode': errorCode,
        'message': message,
        'stackTrace': stackTrace,
        'errors': JsonConverters.toJson(errors, 'List<ResponseError>', context!),
        'meta': meta
      };

  getTypeName() => "ResponseStatus";
  TypeContext? context = _ctx;
}

I could step through the fromMap method and this contained the correct data you loaded from the map. However when the debugger jumps out of the fromMap named constructor, its all empty.

I don’t know what exactly is going on, but I personally use FactoryConstructors. That would look as follows:

  factory ResponseStatus.fromMap(Map<String, dynamic> json) {
    rs = ResponseStatus(
      errorCode = JsonConverters.getJson(json, 'errorCode'),
      message = JsonConverters.getJson(json, 'message'),
      stackTrace = JsonConverters.getJson(json, 'stackTrace'),
      errors = JsonConverters.fromJson(JsonConverters.getJson(json, 'errors'), 'List<ResponseError>', context!),
      meta = JsonConverters.toStringMap(JsonConverters.getJson(json, 'meta')),
    );

    return rs;
  }

I don’t know if it would fix the problem, since that source code is not part of my project.

We’d need a minimal repro to investigate, but since it’s your own custom error handling have you tried using your own Response Error DTO? that way it’s using your generated DTO instead of library DTOs that you can’t control.

Can you repro this by using a JSON string, I.e. populating your request DTO from a JSON string, it would be easier to repro in that case.

Hi @mythz

Eventually this is a misunderstanding. With my own error handling I meant that I just wrote DART code to deal with the Future results I get when calling any async (REST) API call and show a suitable message to the end user. On the server side I follow this recommendation for all my APIs.

So I create my own client models out of the ServiceStack generate DTOs, but only for the data inside the ResponseDTOs, not for all response DTO objects. Let me show you an example of one endpoint to see what is going on.

On the Server I defined the following DTOs to get a single instance of a CreditCard object (just a sample here)

[DataContract]
public class BscGetCreditCard : IBscRequestId, IReturn<BscGetCreditCardResponse>
{
	[ApiMember(Name = "UniqueRequestId", Description = "A unique, client side generated request ID. Best bet is a GUID converted to a string.",
		ParameterType = "path", DataType = "RequestMetaDataDto", IsRequired = true)]
	[DataMember(Order = 1)]
	public string UniqueRequestId { get; set; }

	[ApiMember(Name = "CreditCardId", Description = "ID of the credit card.",
		ParameterType = "path", DataType = "object", IsRequired = true)]
	[DataMember(Order = 2)]
	public string CreditCardId { get; set; }
	
	[ApiMember(Name = "Fields", Description = "List of strings containing the names of credit card properties which should be returned. The `_id` " +
											  "field is ALWAYS returned. Field names are CASE SENSITIVE. Field names which cannot be found are " +
											  "silently ignored.",
		ParameterType = "path", DataType = "Array", IsRequired = false)]
	[DataMember(Order = 3)]
	public List<string> Fields { get; set; }
}

[DataContract]
public class BscGetCreditCardResponse
{
	[DataMember(Order = 1)]
	public BscCreditCard CreditCard { get; set; }
	
	[ApiMember(Name = "CreditCardProperties", Description = "List of key value pairs when `fields` where set in the request. NULL if `Fields` " +
														  "was NULL in the request. Properties which are not found are not returned in " +
														  "the Array.",
		ParameterType = "path", DataType = "Array", IsRequired = false)]
	[DataMember(Order = 2)]
	public Dictionary<string, object> CreditCardProperties { get; set; }
	
	[ApiMember(Name = "ResponseStatus", Description = "Contains exceptions and errors which may have occured during execution of the request. " +
													  "May contain additional information such as warnings in the `Meta` dictionary. NULL if " +
													  "no errors or other status messages where added.",
		ParameterType = "path", DataType = "object", IsRequired = false)]
	[DataMember(Order = 3)]
	public ResponseStatus ResponseStatus { get; set; } 
}

In my APIs I often have an optional fields parameter which allows to filter those property of an object that you need (instead of returning a large complete object). As you can see, the Response object has three members:

  • CreditCard DTO which contains the entire object. This is filled if you did not fill the Fields array with property names of the object. If you did, the CreditCard DTO will be null. It can also be null, if a CreditCardId was submitted that could not be found on the server (rarely the case but possible)
  • Dictionary<string, object> if you submitted the Fields array. In that case the CreditCard DTO will be null and the dictionary returns those properties you requested in a Dictionary (which is serialized by your code to a Map for DART. The dictionary can be null if you submitted unknown property names or if nothing was found (same as above)
  • ResponseStatus which gets all exceptions and errors injected. This is null if no errors occured.

This means I have to test all three possibilities in the list above on the client. The first check is always to test if ResponseStatus is null. If it is not, it means there is an error and usually no data is in the Response object. I then display the errors to the user. If the Response object contains data, I convert this data to my own client models.

So in Dart I usually get a Future<ResponseDTO> back when calling a ServiceStack service. To check the result I have code similar to the following:

  Future<bool> _fetchCreditCard() async {
    print('Fetching credit card data...');
    try {
      var response = await locator<CreditCardService>().getCreditCard(widget.ccId);
      if (response.responseStatus != null) {
        print('There was a server error...');
        //server error
        String message = response.responseStatus?.message as String;
        SnackBar sb = SnackbarBuilder(
                context: context, message: message, durationSeconds: 10, messageType: Enums.infoMessageType.Error)
            .buildSnackBar();
        ScaffoldMessenger.of(context).showSnackBar(sb);
      } else if (response.creditCard != null) {
        print('Got data from server....');
        // success
        serverCc = CreditCard.fromDto(response.creditCard!);
        updatedCc = CreditCard.fromCreditCard(serverCc);
        return true;
      } else {
        String message = 'The returned response did not contain any data, nor did it contain errors.';
        SnackBar sb = SnackbarBuilder(
                context: context, message: message, durationSeconds: 5, messageType: Enums.infoMessageType.Warning)
            .buildSnackBar();
        ScaffoldMessenger.of(context).showSnackBar(sb);
      }

      return false;
    } catch (error) {
      // throw Exception(error);
      print(error.toString());
      return false;
    }
  }

(This method returns a Future<bool> because of Flutter state management, it is called in the initState method of a UI Widget and uses a Builder to show the result or an error message. I don`t elaborate more on this, there are people who have written books about state management…)

But back to what I meant with my own error handling. In the _fetchCreditCard() method you see the checks I mentioned above. In the first if statement I check if response.responseStatus != null which would mean there is an error which I will show in a SnackBar control. Since I called the Request without submitting the fields array, I do not check the Dictionary at all.

So everything works fine if a CreditCard DTO is returned. To test if error messages are correctly displayed, I nulled the returned CreditCard DTO on the server side service and filled the ResponseStatus object of the ResponseDTO manually manually with some fake data.

I saw this working already but I think last week we had updates on Flutter. I will test in other places. But currently it is a matter of fact, that whatever I get back from the .NET Server, the ResponseStatus object is always null which means my error handling in Dart is not working anymore.

UPDATE
Something is going terribly wrong here! I now throw an Exception on the Server instead of returning something. Now this is appears in the catch block of my Dart code, NOT in the response object. The error is of type object but in the debugger I can see that it contains a ResponseStatus:

So this seems to be a problem when using try ... catch in dart and not a FutureBuilder. I need to research this further…

I have added two projects to GitHub which let you reproduce the problem.

I run everything on a MAC (I don’t have Windows), read the README.md files for details.

Please see the section on Error Handling:

Error Response Types

The Error Response that gets returned when an Exception is thrown varies on whether a conventionally-named {RequestDto}Response DTO exists or not.

If it exists:

The {RequestDto}Response is returned, regardless of the service method’s response type. If the {RequestDto}Response DTO has a ResponseStatus property, it is populated otherwise no ResponseStatus will be returned. (If you have decorated the {ResponseDto}Response class and properties with [DataContract]/[DataMember] attributes, then ResponseStatus also needs to be decorated, to get populated).

Otherwise, if it doesn’t:

A generic ErrorResponse gets returned with a populated ResponseStatus property.


So it looks like your issue is that your property is named Status instead of ResponseStatus since the Response DTO will be returned but has no ResponseStatus info to populate, which causes ServiceStack to return the DTO without being able to populate the error information.

If your not using ServiceStack’s built-in error handling (i.e. want to return a 200 OK with your own Status error property) can you use your own Error Response DTO to capture your own error information.

Yes in the provided sampe it is wrong and I fixed it. (In my production version it was correct) So the question is the following:

Should this code work (is it allowed to CREATE a ResponseStatus object MANUALLY and return it in a {RequestDto}Response object as shown below ?):

            return new GetAddressListResponse()
            {
                //Addresses = addresses.Addresses,
                //This is still NULL on Dart
                ResponseStatus = new ResponseStatus("500", "This is a fake message for testing!"),
            };

If you say YES, this does NOT work, in Flutter the object is NULL.

And if I fill the Errors List with a few entries, it is not only null, it crashes.

Interesting enough: I changed my code a bit to generate a “naturally Exception” by accessing a member of the list, which is out of range. This of course creates an exception and in this case the ResponseStatus is filled correctly.

As I explained yesterday, I created a ResponseStatus object manually, to test my error handling routines on the flutter client.

I have updated my source code (both projects) and pushed it to GitHub. You can play around with that code to see what I mean.

To create a manual Error Response you should return it in a HttpError. If you want to return a custom status in a normal Response DTO I’d recommend using your own Error Status types as suggested earlier since this isn’t related to ServiceStack’s built-in error handling.

I’ll look into what the issue is with returning a ResponseStatus object in a Response DTO.

Hi @tbednarz,

When I use your projects, I’m getting the following in your client app.

image

The client is haven’t trouble deserializing the ResponseStatus, or more specifically the ResponseError list, so does seem to be choking on a null even though yes the payload has the data.

Changing your server code to return a HTTP status of 500 results in populating status code just like exception (any code >=400 should do the same). Eg

Request.Response.StatusCode = 500; // Added this line
return new GetAddressListResponse()
{
    //Addresses = addresses.Addresses,
    //This is still NULL on Dart
    //ResponseStatus = new ResponseStatus("500", "This is a fake message for testing!"),
    ResponseStatus = rs,
};

Response status populated as expected when the error handling in the client parses the data successfully.

EDIT: I can’t see in your if you explicitly want to avoid returning a HTTP status code error or just handle in body as 200 as @mythz suggessted, so please my post ignore above if you want to return 200 and handle yourself.

Hi Guys,
Thanks for looking into that. I learned the following:

  1. I was not aware of the fact, that the error code matters (I thought you simply serialize / deserialize those objects, no matter what error code is inside). I created these object for testing my error handling in the Flutter client only.
  2. As @mythz suggested, I use my own status objects which are completely independent of HTTP errors. (I call them ApiResult and they contain optional information which a consumer may or may not use, e.g. informing how many files or folders where deleted when you call a RemoveFolder endpoint in my document management system)
  3. Maybe I misunderstand your Error handling documentation. Let me explain what I mean:

In my service layer on the client I call the following:

  Future<GetAddressListResponse> getAddresses() async {
    var request = GetAddressList();

    try {
      var response = await client.get(request);
      return response;
    } catch (err) {
      return Future.error(err);
    }
  }

I expected to never see any service exceptions (such as WebServiceException) in the catch block above. I expected to see them in the GetAddressListResponse object in the ResponseStatus member only, since my Response object contains a ResponseStatus member. In the above catch block I expected to see only exceptions that occured outside of my backend services such as connection errors, e.g. Socket Errors if the server is not reachable for whatever reasons.

But that is NOT the case! So if you change my server code and undcomment the following line in the GetAddressList Service:

var addrCrash = addresses.Addresses[10];

this will throw an OutOfBoundsException. It appears as WebServiceException in the catch block above!

So maybe you can clarify this. Is it normal, that Exceptions in my backend are thrown as WebServiceExceptions no matter wheter I define a ResponseStatus member in my <RequestDto>Response class or not?

I’ve no idea where you got this expectation from, all WebServiceExceptions have only ever appeared in catch blocks.

All Server Service Exceptions throw WebSericeException’s in all Service Clients. If you use a conventionally named Response DTO (<RequestDto>Response) it will populate the ResponseStatus if it exists, if it doesn’t the error message only exists in the HTTP Status Code and Status Description which is what the WebServiceException has to fallback using, but in this case it can’t populate more structured error information like field validation errors. If you don’t want to make use of ServiceStack’s built-in error handling (i.e. return 200 OK with custom status) then you should use your own Status DTOs so it’s not confused with ServiceStack’s built-in error handling.

Issue with using built-in ResponseStatus should be resolved in latest Dart servicestack 2.0.1 library.

dependencies:
  servicestack: ^2.0.1