Implementar un JsonConverter Personalizado

En este post vamos a ver cómo podemos alterar un json para que se modifique antes de serializar, esta acción la realizaremos principalmente antes de enviar los datos al usuario o al cliente, ya que no tiene sentido hacerlo de forma interna.

 

 

1 - Problema inicial

Lo que vamos a hacer en este post es juntar varios post que tengo creados, vamos a extender la librería de Railway oriented programing para que disponga de la funcionalidad de añadir traducciones a los mensajes de error, similar a lo que vimos en el vídeo de localización e idiomas

 

Ahora mismo si utilizamos la estructura Result<T> dentro de nuestra API, esta nos devolverá en caso de error un Json como el siguiente: 

{
   "Value":null,
   "Errors":[
      {
         "Message":"Mensaje de error",
         "ErrorCode":"d07777ac-b317-44cb-a585-fabd408f37bf"
      }
   ],
   "Success":false
}

Y el código que utilizamos es básicamente algo así: 

ResultDto<PlaceHolder> obj = Result.Failure<PlaceHolder>(Error.Create("Mensaje de error", GuidAleatoria));

 

Pero claro, esto tiene un problema, y es que si queremos responder en varios idiomas, no podemos, o por lo menos no podemos hacerlo de una forma sencilla. 

 

2 - Cómo crear un serializer personalizado en C#

Para ello lo que podemos hacer es un serializer de Json personalizado, lo que quiere decir que cuando vayamos a serializar un elemento, en vez de utilizar el serializer por defecto utilizará uno creado por nosotros. 

 

Para ello lo primero que vamos a hacer es crear un nuevo proyecto, denominado ROP.ApiExtensions.Translations el cual se va a encargar de realizar toda la lógica de estas traducciones. Esta separado para que si lo tienes que copiar en tu propio proyecto, sea sencillo hacerlo.

 

Ahora vamos a crear una clase llamada ErrorDtoSerializer, ya que la API responde un DTO.

 

Esta clase necesita recibir un tipo genérico, el cual será la clase que hace referencia al fichero de la traducción.

 

Y finalmente extenderá de la clase JsonConverter del paquete System.Text.json, que es la encargada de permitirnos cambiar el serializer, una vez lo tenemos todo, nos quedará la siguiente clase: 

public class ErrorDtoSerializer<TTranslationFile> : JsonConverter<ErrorDto>
{
    public override ErrorDto Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }

    public override void Write(Utf8JsonWriter writer, ErrorDto value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}
  • Read -> nos convierte de un json a T
  • Write -> nos convierte de T a json.

 

En nuestro caso, solo nos interesa el método Write, desafortunadamente no podemos dejar el read vacío o llamar al padre, así que lo tendremos que implementar también.

 

2.1 - Convertir de tipo a Json personalizado en C#

Lo que vamos a hacer es modificar la respuesta para que, si el error no contiene un mensaje de error, lo recoja de nuestro fichero de traducciones. 

 

Para ello necesitamos acceso al IHttpContextAccessor, que contiene la información del lenguaje que está utilizando el usuario. y recogeremos su cultureinfo, como vimos en el vídeo de la localización e idiomas ; Nota toda esta parte del código está en dicho post.

public override void Write(Utf8JsonWriter writer, ErrorDto value, JsonSerializerOptions options)
{
    string errorMessageValue = value.Message;
    if (string.IsNullOrWhiteSpace(value.Message))
    {
        CultureInfo language = _httpContextAccessor.HttpContext.Request.Headers.GetCultureInfo();
        errorMessageValue = LocalizationUtils<TTranslationFile>.GetValue(value.ErrorCode.ToString(), language);
    }
}

 

Ahora debemos volver a construir el nuevo mensaje a través del tipo que recibimos writer:

public override void Write(Utf8JsonWriter writer, ErrorDto value, JsonSerializerOptions options)
{
    string errorMessageValue = value.Message;
    if (string.IsNullOrWhiteSpace(value.Message))
    {
        CultureInfo language = _httpContextAccessor.HttpContext.Request.Headers.GetCultureInfo();
        errorMessageValue = LocalizationUtils<TTranslationFile>.GetValue(value.ErrorCode.ToString(), language);
    }
    
    writer.WriteStartObject();
    writer.WriteString(nameof(Error.ErrorCode), value.ErrorCode.ToString());
    writer.WriteString(nameof(Error.Message), errorMessageValue);
    writer.WriteEndObject();
}

Y ya está, con esto ya tenemos la traducción lista.

 

 

2.2 - Convertir de Json a Tipo en C#

Ahora tenemos que hacer la inversa, pasar de un Json a un tipo:

public override ErrorDto Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    string errorMessage = null;
    Guid errorCode = Guid.NewGuid();

    while (reader.Read())
    {
        if (reader.TokenType == JsonTokenType.EndObject)
        {
            break;
        }

        if (reader.TokenType != JsonTokenType.PropertyName)
        {
            throw new JsonException();
        }

        string propertyName = reader.GetString();

        if (propertyName == nameof(ErrorDto.ErrorCode))
        {
            reader.Read();
            errorCode = Guid.Parse(reader.GetString() ?? string.Empty);
        }

        if (propertyName == nameof(ErrorDto.Message))
        {
            reader.Read();
            errorMessage = reader.GetString();
        }

    }

    //theoretically with the translation in place errormessage will never be null
    if (errorMessage == null && errorCode == null)
        throw new Exception("Either Message or the ErrorCode has to be populated into the error");

    return new ErrorDto()
    {
        ErrorCode = errorCode,
        Message = errorMessage
    };
}

 

 

2.3 - Utilizar un serializer personalizado en c#

Con estos cambios ya tenemos nuestro serializer personalizado creado, ahora  únicamente tenemos que utilizarlo.

 

Para ello es tan sencillo como añadirlo a nuestra instrucción  de serializar:

var serializeOptions = new JsonSerializerOptions();
serializeOptions.Converters.Add(new ErrorDtoSerializer<ErrorTranslations>(httpContextAccessorMock.Object));

Y luego como vemos en el test lo podemos utilizar de forma manual

[Fact]
public void When_message_is_empty_then_translate()
{
    Mock<IHeaderDictionary> mockHeader = new Mock<IHeaderDictionary>();
    mockHeader.Setup(a => a["Accept-Language"]).Returns("en;q=0.4");
    Mock<IHttpContextAccessor> httpContextAccessorMock = new Mock<IHttpContextAccessor>();
    httpContextAccessorMock.Setup(a => a.HttpContext.Request.Headers).Returns(mockHeader.Object);

    var serializeOptions = new JsonSerializerOptions();
    serializeOptions.Converters.Add(new ErrorDtoSerializer<ErrorTranslations>(httpContextAccessorMock.Object));

    ResultDto<Unit> obj = new ResultDto<Unit>()
    {
        Value = null,
        Errors = new List<ErrorDto>()
        {
            new ErrorDto()
            {
                ErrorCode = ErrorTranslations.ErrorExample
            }
        }.ToImmutableArray()
    };

    string json = JsonSerializer.Serialize(obj, serializeOptions);
    var resultDto = JsonSerializer.Deserialize<ResultDto<Unit>>(json);
    Assert.Equal("This is the message Translated", resultDto.Errors.First().Message);
}

Pero claro, esto no tiene mucho sentido, lo suyo es que se haga de una forma más o menos automática.

 

 

2.4 - Incluir Custom JsonConverter dentro de los servicios

Lo que podemos hacer es añadirlo a los servicios, el cual cargará la información cuando iniciemos la aplicación que es lo que de verdad renta.

 

Para ello hay tres formas, las dos primeras son las más “comunes” por así llamarlo, depende que tipo de aplicación estes haciendo tendrás disponible el método .AddJsonOptions()  ya bien sea a través de services.Addcontrollers().AddJsonOptions(...) o services.AddMvcCore().AddJsonOptions(...).

 

Y una vez estás ahí añadir la siguiente línea. 

services.AddControllers().AddJsonOptions(options => 
    options.JsonSerializerOptions.Converters.Add(new ErrorDtoSerializer<TranslationFile>(httpContextAccessor)));

 

Como vemos, es bastante código, así que en la propia librería de ROP hay una extensión que te lo permite hacer más fácil:

services.AddControllers().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.AddTranslation<TraduccionErrores>(services);
} );

 

De esta forma pues se facilita bastante su uso.

 

Y aquí podemos ver como funciona en ambos idiomas.

traducciones automaticas con rop

 

Conclusión

En este post hemos visto cómo crear un Serializer de Json personalizado, donde podemos alterar el resultado para así devolver lo que nos interesa. 

 


Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2024 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café