Wstęp

Ostatnio w pracy integrowałem nasz system z zewnętrznym API. Musiałem zintegorać zarządzanie zadaniami klienta. W wyniku czego powstał w kodzie taki enum:

internal enum CustomerTaskStatus
{
    Pending = 1,
    Completed = 2,
    Cancelled = 3,
}

Nie trzeba tłumaczyć, wiadomo o co chodzi 🙂

Z racji tego, że ten status dostaję z zewnętrznego API, nie mam wpływu jakie wartości zwróci. A zwraca takie wartości:

PENDING
COMPLETED
CANCELLED

Domyślny deserializer ASP.NET Core nie poradzi sobie z tym, więc trzeba napisać własny.

Przykład z domyślnym deserializerem

Kod uproszczony na maksa, aby tylko pokazać problem z deserializerem.

using System.Text.Json;
using System.Text.Json.Serialization;

string json = """
              {
                "Status": "PENDING"
              }
              """;


var customerTask = JsonSerializer.Deserialize<CustomerTask>(json);
Console.WriteLine($"Status: {customerTask!.Status}");

// Obiekt do którego deserializujemy
internal sealed class CustomerTask
{
    public CustomerTaskStatus Status { get; set; }
}

internal enum CustomerTaskStatus
{
    Pending = 1,
    Completed = 2,
    Cancelled = 3
}

W takim przypadku dostajemy błąd:

System.Text.Json.JsonException: The JSON value could not be converted to CustomerTaskStatus. Path: $.Status | LineNumber: 1 | BytePositionInLine: 21

ponieważ nie może sobie poradzić z zamianą PENDING na Pending.

Właśny JsonConverter

Dodajemy własny JsonConverter:

internal sealed class CustomerTaskStatusJsonConverter : JsonConverter<CustomerTaskStatus>
{
    /// <inheritdoc />
    public override CustomerTaskStatus Read(ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options)
    {
        return Enum.Parse<CustomerTaskStatus>(reader.GetString()!, true);
    }

    /// <inheritdoc />
    public override void Write(Utf8JsonWriter writer,
        CustomerTaskStatus value,
        JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString().ToUpper());
    }
}

Nasz konwerter musi dziedziczyć po generecznym JsonConverter<CustomerTaskStatus>, gdzie przekazujemy naszego enuma. Musimy napisać implementację 2 metoda:

  • Read - która jest używana przy deserializacji. W naszym przypadku używam Enum.Parse, gdzie ostatni parametr jest true. A ten parametr oznacza właśnie, aby zignorował wielkość liter.
  • Write - które jest używana przy serializji

Jeszcze tylko musimy oznaczyć naszą właściwość, aby użyć tego convertera

internal sealed class CustomerTask
{
    [JsonConverter(typeof(CustomerTaskStatusJsonConverter))]
    public CustomerTaskStatus Status { get; set; }
}

I to tyle 😄 Wszystko działa. Można wrócić do dalszej integracji zewnętrznego API 🙂

Cały kod tego przykładu jest dostępny na GitHubie: https://gist.github.com/tomaszprasolek/930f600f9d414045c0088c6c2d718fe7

Linki