c# - Json.NET serializa enum como cadena incluso con configuraciones predeterminadas
(2)
RAZÓN
El comportamiento que vemos aquí es por diseño, las configuraciones aprobadas en los métodos de JsonConvert
se fusionan con DefaultSettings
proporcionados. Aquí hay una pequeña parte del código fuente de Json.Net de la clase JsonSerializer para desmitificar la situación.
private static void ApplySerializerSettings(JsonSerializer serializer, JsonSerializerSettings settings)
{
if (!CollectionUtils.IsNullOrEmpty(settings.Converters))
{
// insert settings converters at the beginning so they take precedence
// if user wants to remove one of the default converters they will have to do it manually
for (int i = 0; i < settings.Converters.Count; i++)
{
serializer.Converters.Insert(i, settings.Converters[i]);
}
}
Como podemos ver, los conversores se fusionan con los predeterminados proporcionados, aunque con mayor precedencia.
Solución
Podemos aprovechar la mayor precedencia de los conversores fusionados recientemente y prevenir la conversión de StringEnumConverter
.
class PreventStringEnumConverter : StringEnumConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var type = value.GetType();
var undertype = Enum.GetUnderlyingType(type);
var converted=Convert.ChangeType(value, undertype);
writer.WriteValue(converted);
}
}
y el uso sería como:
var json = JsonConvert.SerializeObject(obj,
new JsonSerializerSettings
{
Converters = new List<JsonConverter> {new PreventStringEnumConverter()}
});
Estoy usando Json.NET 7.0.1.
La documentación dice que
Enum
[se serializa como] entero (puede ser el nombre del valor enum con StringEnumConverter)
En mi Global.asax.cs, especifico la configuración predeterminada de la siguiente manera:
JsonConvert.DefaultSettings = (() =>
{
var settings = new JsonSerializerSettings();
settings.Converters.Add(new StringEnumConverter());
return settings;
});
Sin embargo, en ciertas situaciones, quiero que los Enums
como enteros, por ejemplo, cuando construyo un JSON que se va a almacenar en la base de datos.
Así es como lo hice:
public class JsonSerializedType<T> : IUserType where T : class
{
private static readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
cmd.Parameters[index].Value = JsonConvert.SerializeObject(value as T, serializerSettings);
}
}
Sin embargo, incluso en este caso, los Enums
se serializan como cadenas. Comprobé que serializerSettings
no tiene Converters
y ContractResolver
es null
.
¿Por qué es así?
Esto está sucediendo porque JsonConvert.SerializeObject
aplica las configuraciones entrantes sobre la configuración predeterminada , llamando internamente a JsonSerializer.CreateDefault(JsonSerializerSettings settings)
. No sé si / dónde está documentado este comportamiento, pero está visible en el código fuente . Por lo tanto, la lista predeterminada de convertidores se usará además de la lista vacía de convertidores localmente especificados, lo que significa que se StringEnumConverter
el valor predeterminado StringEnumConverter
.
Tienes un par de opciones para solucionar esto:
Construya el
JsonSerializer
usted mismo sin usar la configuración predeterminada:public static class JsonExtensions { public static string SerializeObjectNoDefaultSettings(object value, Formatting formatting, JsonSerializerSettings settings) { var jsonSerializer = JsonSerializer.Create(settings); jsonSerializer.Formatting = formatting; StringBuilder sb = new StringBuilder(256); StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture); using (JsonTextWriter jsonWriter = new JsonTextWriter(sw)) { jsonWriter.Formatting = jsonSerializer.Formatting; jsonSerializer.Serialize(jsonWriter, value); } return sw.ToString(); } }
Y luego úsalo como:
var json = JsonExtensions.SerializeObjectNoDefaultSettings(value, Formatting.None, new JsonSerializerSettings());
Reemplazar el
StringEnumConverter
defecto con uno que no serialice las enumeraciones como cadenas, por ejemplo:public class IntegerEnumConverter : StringEnumConverter { public override bool CanRead { get { return false; } } public override bool CanWrite { get { return false; } } }
Y luego úsalo como:
var json = JsonConvert.SerializeObject(value, Formatting.None, new JsonSerializerSettings { Converters = new JsonConverter[] { new IntegerEnumConverter() } });
El convertidor especificado localmente se seleccionará con preferencia al convertidor predeterminado.
Anular temporalmente el JsonConvert.DefaultSettings
sería una mala idea ya que no sería seguro para subprocesos.