Error with GUID value in request in ServiceStack v8

Hello,

I’ve got a weird behavior when transfering GUID values over JsonServiceClient after updating API to ServiceStack v8.

When I call request, that has GUID value in body, it fails with error:

400 Bad Request
Code: SerializationException, Message: Could not deserialize 'application/json' request using SoftProgres.Web.ServiceModel.Dtos.UpdateProductInstallationLogin'
Error: The JSON value could not be converted to System.Guid. Path: $.Id | LineNumber: 0 | BytePositionInLine: 54.
Meta:
  InnerMessages: The JSON value could not be converted to System.Guid. Path: $.Id | LineNumber: 0 | BytePositionInLine: 54.
The JSON value is not in a supported Guid format.
Server StackTrace:
 System.Runtime.Serialization.SerializationException: Could not deserialize 'application/json' request using SoftProgres.Web.ServiceModel.Dtos.UpdateProductInstallationLogin'
Error: The JSON value could not be converted to System.Guid. Path: $.Id | LineNumber: 0 | BytePositionInLine: 54.
 ---> System.Text.Json.JsonException: The JSON value could not be converted to System.Guid. Path: $.Id | LineNumber: 0 | BytePositionInLine: 54.
 ---> System.FormatException: The JSON value is not in a supported Guid format.
   at System.Text.Json.ThrowHelper.ThrowFormatException(DataType dataType)
   at System.Text.Json.Utf8JsonReader.GetGuid()
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, Utf8JsonReader& reader, Exception ex)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.ContinueDeserialize(ReadBufferState& bufferState, JsonReaderState& jsonReaderState, ReadStack& readStack)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsync(Stream utf8Json, CancellationToken cancellationToken)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsObjectAsync(Stream utf8Json, CancellationToken cancellationToken)
   at ServiceStack.Host.Handlers.ServiceStackHandlerBase.CreateContentTypeRequestAsync(IRequest httpReq, Type requestType, String contentType) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/Host/Handlers/ServiceStackHandlerBase.cs:line 232
   --- End of inner exception stack trace ---
   at ServiceStack.Host.Handlers.ServiceStackHandlerBase.CreateContentTypeRequestAsync(IRequest httpReq, Type requestType, String contentType) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/Host/Handlers/ServiceStackHandlerBase.cs:line 249
   at ServiceStack.Host.RestHandler.CreateRequestAsync(IRequest httpReq, IRestPath restPath, Dictionary`2 requestParams) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/Host/RestHandler.cs:line 156
   at ServiceStack.Host.RestHandler.CreateRequestAsync(IRequest httpReq, IRestPath restPath) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/Host/RestHandler.cs:line 145
   at ServiceStack.Host.RestHandler.ProcessRequestAsync(IRequest req, IResponse httpRes, String operationName) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/Host/RestHandler.cs:line 90

System.Text.Json.JsonException: The JSON value could not be converted to System.Guid. Path: $.Id | LineNumber: 0 | BytePositionInLine: 54.
 ---> System.FormatException: The JSON value is not in a supported Guid format.
   at System.Text.Json.ThrowHelper.ThrowFormatException(DataType dataType)
   at System.Text.Json.Utf8JsonReader.GetGuid()
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, Utf8JsonReader& reader, Exception ex)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.ContinueDeserialize(ReadBufferState& bufferState, JsonReaderState& jsonReaderState, ReadStack& readStack)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsync(Stream utf8Json, CancellationToken cancellationToken)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsObjectAsync(Stream utf8Json, CancellationToken cancellationToken)
   at ServiceStack.Host.Handlers.ServiceStackHandlerBase.CreateContentTypeRequestAsync(IRequest httpReq, Type requestType, String contentType) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/Host/Handlers/ServiceStackHandlerBase.cs:line 232

System.FormatException: The JSON value is not in a supported Guid format.
   at System.Text.Json.ThrowHelper.ThrowFormatException(DataType dataType)
   at System.Text.Json.Utf8JsonReader.GetGuid()
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)

This is how the request looks after calling request.ToJson() in client:

{
  "ProductId" : 1,
  "Id" : "eaca59b40b974e4ca6738a304644f96f"
}

Some screenshots from code:

Everything works fine, when I call it from ServiceStack UI:

I tried to add some request filters and JsConfig in AppHost.cs:

JsConfig<Guid>.DeSerializeFn = guid => new Guid(guid);
  
GlobalRequestFilters.Add((req, res, requestDto) => 
{
    
});     

but neither of these is called when dealing with this request from the client, it seems to fail before entering ServiceStack pipeline. When I call it from ServiceStack UI, both are executed, first DeSerializeFn, then GlobalRequestFilter.

This code is in production on ServiceStack v6, where it seems to work fine.

I am now using ServiceStack 8.5.2 on the API side (.NET 8) and ServiceStack.Client 8.5.2 on desktop (.NET Framework 4.7.2).

I guess I am again missing some configuration in client. Do you have any idea what could help?

Thank you for your response.

The serialzation exception is from System.Text JSON which is more strict in parsing values, i.e. it should be using the default guid.ToString("D") format.

Generally you should use the same JSON serializer and deserializer, i.e. by using JsonApiClient.

But if you have to support existing clients you can control when to use System.Text.Json for only serializing an APIs response with:

[SystemJson(UseSystemJson.Response)]
public class MyRequest { }

Or not use it for that API with:

[SystemJson(UseSystemJson.Never)]
public class MyRequest { }

Where it will fallback to using the more resilient ServiceStack.Text JSON serializer instead.

1 Like

Thank you, this works, but is there a way how to configure it globally to UseSystemJson.Never for every service?

Because configuring it manually for every service having guid is not the best solution.

Or is there a way how to configure JsonServiceClient to send guids in guid.ToString("D") format?

Docs for System.Text.Json APIs. You can configure when to use System.Text.Json for APIs when registering to use Endpoint Routing:

app.UseServiceStack(new AppHost(), options => {
    // Use for Serialization and Deserialization of JSON APIs (default)
    options.MapEndpoints(useSystemJson:UseSystemJson.Always);

    // Use only for deserializing API Requests
    options.MapEndpoints(useSystemJson:UseSystemJson.Request);

    // Use only for serializing API Responses
    options.MapEndpoints(useSystemJson:UseSystemJson.Response);

    // Don't use System.Text.Json for APIs
    options.MapEndpoints(useSystemJson:UseSystemJson.Never);
});

See docs for JSON Format for how to customize the JSON Serializer, e.g:

JsConfig<Guid>.SerializeFn = guid => guid.ToString("D");
1 Like

Oh snap! For some reason I didn’t realize that I can use global JsConfig in client code. I was still looking for a way to configure it on JsonServiceClient object. This solved the problem, thanks!