ASP.NET Core:come modificare le impostazioni di serializzazione JSON

ASP.NET Core:come modificare le impostazioni di serializzazione JSON

System.Text.Json è il serializzatore JSON predefinito in ASP.NET Core. Utilizza le seguenti impostazioni di serializzazione predefinite:

var options = new JsonSerializerOptions()
{
	PropertyNameCaseInsensitive = true,
	PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
	NumberHandling = JsonNumberHandling.AllowReadingFromString
};
Code language: C# (cs)

Per modificare le impostazioni a livello di servizio per tutti i controller, chiama AddJsonOptions() in Startup.ConfigureServices() in questo modo:

public class Startup
{
	//rest of class
	public void ConfigureServices(IServiceCollection services)
	{
		services.AddControllers().AddJsonOptions(j => 
		{
			j.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
		});
		
		//rest of method
	}
}
Code language: C# (cs)

Nota:questo esempio sta passando JsonStringEnumConverter, il che fa sì che JsonSerializer utilizzi il nome enum anziché il valore enum.

La modifica delle impostazioni a livello di servizio si applica a tutti i controller. Quando una richiesta arriva con JSON, applicherà le tue impostazioni durante la deserializzazione della richiesta. Allo stesso modo, quando restituisci una risposta, utilizzerà le tue impostazioni per serializzare il modello nella risposta.

Puoi anche modificare le impostazioni a livello di azione (ma solo per la serializzazione) ea livello di controller, che mostrerò di seguito.

Modifica le impostazioni JSON a livello di azione (solo serializzazione)

Supponiamo di voler modificare le impostazioni di serializzazione JSON per un'azione specifica in un controller in modo che utilizzi JsonStringEnumConverter.

Esistono due modi per farlo:restituire JsonResult e passargli un oggetto JsonSerializerOptions oppure utilizzando direttamente JsonSerializer. Mostrerò entrambi gli approcci di seguito.

Ci sono alcuni difetti in questo approccio.

  • Non puoi modificare le impostazioni per la deserializzazione.
  • Il parametro delle opzioni del costruttore JsonResult è di tipo oggetto . In realtà richiede un oggetto JsonSerializerOptions, ma non lo applica in fase di compilazione, il che significa che è di tipo non sicuro e può portare a eccezioni di runtime.
  • È necessario creare nuove istanze di JsonSerializerOptions ogni volta, il che è negativo perché il riutilizzo dello stesso oggetto opzioni porta a un aumento di velocità di 200 volte nella serializzazione. Nota:in alternativa è possibile aggiungere una proprietà statica nel controller, o forse la dipendenza iniettare un oggetto opzioni singleton.

Potresti considerare questo approccio solo come ultima risorsa. Se hai solo bisogno di modificare una singola azione, questo potrebbe andare bene. Se devi modificare più azioni in un controller, ti consiglio invece di utilizzare l'approccio a livello di controller.

Opzione 1 – Restituisci JsonResult

Puoi personalizzare la serializzazione restituendo un JsonResult e passando un oggetto JsonSerializerOptions, in questo modo:

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

[HttpGet("{symbol}")]
public async Task<IActionResult> Get(string symbol)
{
	var stock = await GetStockFromRepo(symbol);

	var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
	options.Converters.Add(new JsonStringEnumConverter());

	return new JsonResult(stock, options)
	{
		StatusCode = (int)HttpStatusCode.OK
	};
}
Code language: C# (cs)

Nota:nota che sta passando JsonSerializerDefaults.Web nel costruttore. Questo serve per assicurarsi che utilizzi i valori predefiniti utilizzati normalmente da ASP.NET Core.

Quando viene chiamato questo endpoint, il framework utilizza l'oggetto opzioni passato per serializzare l'oggetto risposta. Nota che ha il nome enum invece del valore:

{
    "symbol": "AMZN",
    "price": 101.1,
    "quoteTime": "2021-07-23T15:13:16.3911373-04:00",
    "fundType": "Stock"
}
Code language: JSON / JSON with Comments (json)

Opzione 2:usa direttamente JsonSerializer

Niente ti impedisce di utilizzare direttamente JsonSerializer con il tuo oggetto JsonSerializerOptions:

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

[HttpGet("{symbol}")]
public async Task<IActionResult> Get(string symbol)
{
	var stock = await GetStockFromRepo(symbol);

	var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
	options.Converters.Add(new JsonStringEnumConverter());

	return new ContentResult()
	{
		StatusCode = (int)HttpStatusCode.OK,
		ContentType = "application/json",
		Content = JsonSerializer.Serialize<Stock>(stock, options)
	};
}
Code language: C# (cs)

Quando viene chiamato l'endpoint, restituisce il seguente JSON con il nome enum anziché il valore:

{
    "symbol": "AMZN",
    "price": 101.1,
    "quoteTime": "2021-07-23T15:39:11.4887762-04:00",
    "fundType": "Stock"
}
Code language: JSON / JSON with Comments (json)

Questo è l'approccio più noioso, ma ti dà il pieno controllo.

Modifica le impostazioni JSON a livello di controller (inclusa la deserializzazione)

Supponiamo che tu voglia modificare le impostazioni JSON per tutte le azioni nel seguente controller denominato StocksController:

[ApiController]
[Route("[controller]")]
public class StocksController : ControllerBase
{
	[HttpGet("{symbol}")]
	public async Task<IActionResult> Get(string symbol)
	{
		var stock = await GetStockFromRepo(symbol);
		return Ok(stock);
	}
	[HttpPost()]
	public async Task<IActionResult> Post(Stock stock)
	{
		await SaveToRepo(stock);
		return Ok($"Posted stock {stock.Symbol}");
	}
}
Code language: C# (cs)

Vuoi che le impostazioni JSON si applichino alla serializzazione (risposta GET) e alla deserializzazione (richiesta POST).

Si noti che StocksController si occupa solo del modello Stock. Supponiamo che nessun altro controller abbia a che fare con il modello Stock. Ciò significa che puoi creare un convertitore personalizzato per il tipo Stock e passarlo nelle impostazioni JSON a livello di servizio. Quando il framework deve gestire la serializzazione/deserializzazione del tipo Stock, verrà delegato al tuo convertitore personalizzato. Ciò significa effettivamente che il convertitore personalizzato verrà utilizzato specificamente per la gestione della serializzazione/deserializzazione per StocksController.

Con questo approccio, non è necessario modificare il controller. Ciò ti consente di aderire al Principio di apertura/chiusura , che afferma che il codice dovrebbe essere aperto all'estensione, ma non alla modifica.

Mostrerò passo dopo passo come eseguire questo approccio di seguito.

Passaggio 1:crea il convertitore personalizzato

Crea il convertitore personalizzato per il tipo Stock, aggiungi una proprietà JsonSerializerOptions denominata ConverterOptions e implementa Read() e Write() come wrapper per l'utilizzo di JsonSerializer direttamente con ConverterOptions.

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

public class StocksConverter : JsonConverter<Stock>
{
	private readonly JsonSerializerOptions ConverterOptions;
	public StocksConverter(JsonSerializerOptions converterOptions)
	{
		ConverterOptions = converterOptions;
	}
	public override Stock Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
	{
		//Very important: Pass in ConverterOptions here, not the 'options' method parameter.
		return JsonSerializer.Deserialize<Stock>(ref reader, ConverterOptions);
	}

	public override void Write(Utf8JsonWriter writer, Stock value, JsonSerializerOptions options)
	{
		//Very important: Pass in ConverterOptions here, not the 'options' method parameter.
		JsonSerializer.Serialize<Stock>(writer, value, ConverterOptions);
	}
}
Code language: C# (cs)

Alcune cose:

  • Il riutilizzo di un oggetto JsonSerializerOptions porta a una serializzazione e deserializzazione 200 volte più veloce. Lo scopo della proprietà ConverterOptions è di poterla riutilizzare ripetutamente.
  • Puoi passare ConverterOptions come parametro del costruttore o semplicemente codificarlo. Preferisco passarlo.
  • Come indicato nei commenti in Read() / Write(), non passare le opzioni per Deserializzare()/Serializzare(). Il motivo è perché opzioni contiene un riferimento al tuo convertitore personalizzato. Se provassi a usarlo, risulterebbe in un ciclo infinito.

Fase 2:passa al convertitore personalizzato a livello di servizio

In AddJsonOptions(), passa un nuovo oggetto JsonSerializerOptions a un'istanza di StocksConverter. Usa le impostazioni che desideri. Questo esempio utilizza JsonStringEnumConverter.

Quindi aggiungi l'oggetto StocksConverter al JsonSerializerOptions principale.

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

public class Startup
{
	//rest of class
	
	public void ConfigureServices(IServiceCollection services)
	{
		services.AddControllers().AddJsonOptions(j =>
		{
			var stockConverterOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);
			stockConverterOptions.Converters.Add(new JsonStringEnumConverter());
			var stockConverter = new StocksConverter(stockConverterOptions);

			j.JsonSerializerOptions.Converters.Add(stockConverter);
		});
		
		//rest of method
	}

}
Code language: C# (cs)

Fase 3:invia le richieste per vederlo funzionare

Invia una richiesta GET (nota:sto usando Postman) :

GET https://localhost:12345/Stocks/AMZNCode language: plaintext (plaintext)

Questo restituisce il seguente JSON. Nota che sta usando il nome enum invece del valore, il che significa che ha utilizzato correttamente StocksConverter con le impostazioni personalizzate:

{
    "symbol": "AMZN",
    "price": 101.1,
    "quoteTime": "2021-07-23T16:57:15.7972445-04:00",
    "fundType": "Stock"
}
Code language: JSON / JSON with Comments (json)

Invia una richiesta POST utilizzando il nome enum:

POST https://localhost:12345/Stocks/AMZN
Body: 
{
    "symbol": "AMZN",
    "price": 102.34,
    "quoteTime": "2021-07-23T16:57:15.7972445-04:00",
    "fundType": "Stock"
}
Code language: plaintext (plaintext)

Questo restituisce la seguente risposta:

Status: OK
Body: Posted stock AMZNCode language: plaintext (plaintext)

Come facciamo a sapere che ha utilizzato StocksConverter con le impostazioni personalizzate? Perché System.Text.Json non gestisce i nomi enum per impostazione predefinita. Se avesse utilizzato le impostazioni predefinite del serializzatore, avrebbe prodotto questa risposta di errore:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "0HMAE3K7U3GFM:00000001",
    "errors": {
        "$.fundType": [
            "The JSON value could not be converted to Models.FundTypes. Path: $.fundType | LineNumber: 4 | BytePositionInLine: 23."
        ]
    }
}
Code language: JSON / JSON with Comments (json)

Modifica le impostazioni di Newtonsoft

Aggiornamento 22-04-2022 – Aggiunta questa sezione grazie al lettore Nestor che ha menzionato la modifica delle impostazioni di Newtonsoft.

System.Text.Json è il serializzatore JSON predefinito in ASP.NET Core. Supponiamo che tu voglia invece utilizzare Newtonsoft e desideri modificare le impostazioni.

Innanzitutto, installa il pacchetto Microsoft.AspNetCore.Mvc.NewtonsoftJson (questo sta usando Package Manager Console in Visual Studio ):

Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson
Code language: PowerShell (powershell)

Ora chiama services.AddControllers().AddNewtonsoftJson() in Startup.ConfigureServices(), modificando facoltativamente le impostazioni di Newtonsoft, in questo modo:

using Newtonsoft.Json.Serialization;

public class Startup
{
	//rest of class
	
	public void ConfigureServices(IServiceCollection services)
	{
		services.AddControllers().AddNewtonsoftJson(options =>
		{
			//customize settings here. For example, change the naming strategy

			options.SerializerSettings.ContractResolver = new DefaultContractResolver()
			{
				NamingStrategy = new SnakeCaseNamingStrategy()
			};
		});
		
		//rest of method
	}

}
Code language: C# (cs)

Ora invia una richiesta:

GET https://localhost:12345/booksCode language: plaintext (plaintext)

Restituisce il seguente JSON con nomi di proprietà in serpente, mostrando che sta usando Newtonsoft come configurato sopra:

{
    "title": "Code",
    "subtitle": "The Hidden Language of Computer Hardware and Software",
    "author_name": "Charles Petzold",
    "date_first_published": "2000-10-11T00:00:00"
}Code language: JSON / JSON with Comments (json)