Multiple JsonServiceClient-s and JsConfig

Hello, I have a question about functionality of JsConfig between multiple JsonServiceClient objects.

We have currently 2 API endpoints that we connect to. Lets say api1.website.com and api2.website.com. First API is older and is using DateTime in /Date(ux_timestamp)/ format, new API is using ISO8601 format by default.

Each API has their own databases, authentication, infrastructure, lets just say that there is no connection between them.

We have two separate JsonServiceClient objects, one for each API.

Is there any way to configure JsConfig and primarilly JsConfig<T> for SerializeFn and DeserializeFn for each JsonServiceClient object separatelly? I know about using scopes and query parameters, but thats really impracticall for large codebase and (at least I did not found it) it doesn’t even allow us to configure generic JsConfig<T> functions SerializeFn and DeserializeFn.

Thank you for your answer.

Are these ServiceStack endpoints? Because you should still be able to send ISO8601 dates to either.

Yes, they are both ServiceStack endpoints. Your statement is true, it really can parse both MS WCF format and ISO8601, my fault for not realizing this. So if the server doesn’t override the serialization or deserialization function and is not forcing client to use some specific formats, which ServiceStack cannot recognize, everything works fine.

But I wanted to focus my question at some cases, where it doesn’t work this way and it may become a problem. :slightly_smiling_face:

Let’s say, Endpoint1 doesn’t override serialization/deserialization functions at all and uses only default JsConfig values.

Endpoint2 overrides some configurations and serialization/deserialization functions in JsConfig and JsConfig<T>, e.g. it uses

JsConfig.IncludeNullValues = true;

and has setup some “crazy” outbound serialization like:

JsConfig<DateTime>.SerializeFn = time => new DateTime(time.Ticks, DateTimeKind.Local).ToString("yyyy MMMM dd", CultureInfo.CurrentCulture);
JsConfig<DateTime?>.SerializeFn = time => time.HasValue ? new DateTime(time.Value.Ticks, DateTimeKind.Local).ToString("yyyy MMMM dd", CultureInfo.CurrentCulture) : null;

I created two identical test services on each endpoint (in my local debug environment) to demonstrate:

public class TestData
{
    public DateTime DateTimeNow { get; set; } = DateTime.Now;
    public DateTime? DateTimeNowNullable { get; set; } = DateTime.Now;
    public DateTime? DateTimeNowNullableNull  { get; set; } = null;
}

[Route("/api/test", "GET")]
public class GetTestData : IReturn<GetTestDataResponse> {}

public class GetTestDataResponse
{
    public TestData TestData { get; set; }
}

public class TestService : Service
{
    public GetTestDataResponse Get(GetTestData getTestData)
    {
        return new GetTestDataResponse()
        {
            TestData = new TestData()
        };
    }
}

Endpoint 1 response:

Endpoint 2 response:

I have generated 2 DTOs (through x csharp tool), one with global namespace Endpoint1, second with global namespace Endpoint2.

Now I create 2 clients:

var endpoint1Client = new JsonServiceClient("http://localhost:8001");
var endpoint2Client = new JsonServiceClient("http://localhost:8002");

and call get:

var testDataResponse1 = endpoint1Client.Get(new Endpoint1.GetTestData());
var testDataResponse2 = endpoint2Client.Get(new Endpoint2.GetTestData());

The resulting deserialized data looks like this - Endpoint2 sends Date in format, that is not recognized and cannot be parsed.

image

The obvious solution would be to use JsConfig to override deserialization from unrecognized format on client side:

JsConfig<DateTime>.DeSerializeFn = time => DateTime.TryParseExact(time, "yyyy MMMM dd", CultureInfo.CurrentCulture, DateTimeStyles.None,  out var parsedDate) ? parsedDate : default;
JsConfig<DateTime?>.DeSerializeFn = time => time != null && DateTime.TryParseExact(time, "yyyy MMMM dd", CultureInfo.CurrentCulture, DateTimeStyles.None, out var parsedDate) ? parsedDate : null;

but as expected, now the problem just swaps, since this deserialization is also applied to the Endpoint1 calls:

image

I am basically looking for a non-static way to configure JsonServiceClient with some ServiceStack.Text JsConfig, that only applies to that specific JsonServiceClient instance and does not affect anything outside of it, without need of using scopes or anything, that must be exhaustively used at each specific call. Initialized only once e.g. in JsonServiceClient constructor, and automatically applied by default at each internal use of serialization/deserialization.

This may also be usefull for OrmLite, since one may want to save/retrieve serialized data in specific formats, but not to use these formats outside of database calls.

The ServiceClients and JSON Serialization evolves carefully to ensure any changes are backwards-compatible so you shouldn’t need to customize the JsonServiceClients to handle old and new versions.

If you change the defaults then you risk breaking most clients relying on defaults, so the recommendation would be that any custom deserialization also handles the default deserialization values to support the majority of existing clients.

The only way to use a different non static JSON configuration is to use a Custom Config Scope as done by the Stripe Gateway to handle’s Stripe’s unique requirements, when serializing/deserializing HTTP Responses:

Which you should also be able to do by creating another custom JsonServiceClient by subclassing ServiceClientBase with overridden SerializeToStream and DeserializeFromStream methods which uses the custom scopes.

This isn’t required for OrmLite which has it’s own pluggable complex type serializers that can be used to change which serialization implementation to use.

I understand, we will stick to only using formats, that ServiceStack can handle without implementing custom deserialization. Thanks for your answer.