c# - serialize - newtonsoft.json example
Nombre de propiedad dinĂ¡mica de Newtonsoft JSON (5)
Aquí hay una solución que no requiere ningún cambio en la forma en que usa el serializador Json. De hecho, también debería funcionar con otros serializadores. Utiliza la clase DynamicObject cool.
El uso es como querías:
var usersPagedData = new PagedData<User>("Users");
....
public class PagedData<T> : DynamicObject
{
private string _name;
public PagedData(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
_name = name;
}
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
public override IEnumerable<string> GetDynamicMemberNames()
{
yield return _name;
foreach (var prop in GetType().GetProperties().Where(p => p.CanRead && p.GetIndexParameters().Length == 0 && p.Name != nameof(Data)))
{
yield return prop.Name;
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (binder.Name == _name)
{
result = Data;
return true;
}
return base.TryGetMember(binder, out result);
}
}
¿Hay alguna manera de cambiar el nombre de la propiedad de datos durante la serialización, por lo que puedo reutilizar esta clase en mi WEB Api?
Por ejemplo, si estoy devolviendo una lista de usuarios paginados, la propiedad de Datos debe ser serializada como "usuarios", si estoy devolviendo una lista de artículos, debería llamarse "artículos", etc.
Es algo como esto posible:
public class PagedData
{
[JsonProperty(PropertyName = "Set from constructor")]??
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
}
EDITAR:
Me gustaría tener un control sobre esta funcionalidad, como el nombre de paso que se usará si es posible. Si mi class
se llama UserDTO
, todavía quiero que la propiedad serializada se llame Users
, no UserDTOs
.
Ejemplo
var usersPagedData = new PagedData("Users", params...);
Echa un vistazo aquí: Cómo cambiar el nombre de la clave JSON
No se realiza durante la serialización pero con una operación de cadena.
No muy agradable (en mis ojos) pero al menos una posibilidad.
Saludos Thomas
La siguiente es otra solución probada en .NET Standard 2.
public class PagedResult<T> where T : class
{
[JsonPropertyNameBasedOnItemClassAttribute]
public List<T> Results { get; set; }
[JsonProperty("count")]
public long Count { get; set; }
[JsonProperty("total_count")]
public long TotalCount { get; set; }
[JsonProperty("current_page")]
public long CurrentPage { get; set; }
[JsonProperty("per_page")]
public long PerPage { get; set; }
[JsonProperty("pages")]
public long Pages { get; set; }
}
Estoy usando Humanizer para la pluralización.
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
{
Type[] arguments = property.DeclaringType.GenericTypeArguments;
if(arguments.Length > 0)
{
string name = arguments[0].Name.ToString();
property.PropertyName = name.ToLower().Pluralize();
}
return property;
}
return base.CreateProperty(member, memberSerialization);
}
Otra opción sin necesidad de jugar con los formateadores json o usar reemplazos de cadenas: solo herencia y reemplazo (todavía no es una solución muy buena, imo):
public class MyUser { }
public class MyItem { }
// you cannot use it out of the box, because it''s abstract,
// i.e. only for what''s intended [=implemented].
public abstract class PaginatedData<T>
{
// abstract, so you don''t forget to override it in ancestors
public abstract IEnumerable<T> Data { get; }
public int Count { get; }
public int CurrentPage { get; }
public int Offset { get; }
public int RowsPerPage { get; }
public int? PreviousPage { get; }
public int? NextPage { get; }
}
// you specify class explicitly
// name is clear,.. still not clearer than PaginatedData<MyUser> though
public sealed class PaginatedUsers : PaginatedData<MyUser>
{
// explicit mapping - more agile than implicit name convension
[JsonProperty("Users")]
public override IEnumerable<MyUser> Data { get; }
}
public sealed class PaginatedItems : PaginatedData<MyItem>
{
[JsonProperty("Items")]
public override IEnumerable<MyItem> Data { get; }
}
Puedes hacer esto con un ContractResolver
personalizado. El resolutor puede buscar un atributo personalizado que indique que desea que el nombre de la propiedad JSON se base en la clase de los elementos en el enumerable. Si la clase de artículo tiene otro atributo que especifica su nombre plural, ese nombre se usará para la propiedad enumerable, de lo contrario, el nombre de la clase del artículo en sí se pluralizará y se usará como el nombre de la propiedad enumerable. A continuación se muestra el código que necesita.
Primero vamos a definir algunos atributos personalizados:
public class JsonPropertyNameBasedOnItemClassAttribute : Attribute
{
}
public class JsonPluralNameAttribute : Attribute
{
public string PluralName { get; set; }
public JsonPluralNameAttribute(string pluralName)
{
PluralName = pluralName;
}
}
Y luego el resolvedor:
public class CustomResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty prop = base.CreateProperty(member, memberSerialization);
if (prop.PropertyType.IsGenericType && member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
{
Type itemType = prop.PropertyType.GetGenericArguments().First();
JsonPluralNameAttribute att = itemType.GetCustomAttribute<JsonPluralNameAttribute>();
prop.PropertyName = att != null ? att.PluralName : Pluralize(itemType.Name);
}
return prop;
}
protected string Pluralize(string name)
{
if (name.EndsWith("y") && !name.EndsWith("ay") && !name.EndsWith("ey") && !name.EndsWith("oy") && !name.EndsWith("uy"))
return name.Substring(0, name.Length - 1) + "ies";
if (name.EndsWith("s"))
return name + "es";
return name + "s";
}
}
Ahora puede decorar la propiedad de nombre variable en su PagedData<T>
con el atributo [JsonPropertyNameBasedOnItemClass]
:
public class PagedData<T>
{
[JsonPropertyNameBasedOnItemClass]
public IEnumerable<T> Data { get; private set; }
...
}
Y decora tus clases DTO con el atributo [JsonPluralName]
:
[JsonPluralName("Users")]
public class UserDTO
{
...
}
[JsonPluralName("Items")]
public class ItemDTO
{
...
}
Finalmente, para serializar, cree una instancia de JsonSerializerSettings
, establezca la propiedad ContractResolver
y pase la configuración a JsonConvert.SerializeObject
manera:
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CustomResolver()
};
string json = JsonConvert.SerializeObject(pagedData, settings);
Fiddle: https://dotnetfiddle.net/GqKBnx
Si está utilizando Web API (parece que lo está), entonces puede instalar el programa de resolución personalizado en la tubería a través del método de Register
de la clase WebApiConfig
(en la carpeta App_Start
).
JsonSerializerSettings settings = config.Formatters.JsonFormatter.SerializerSettings;
settings.ContractResolver = new CustomResolver();
Otro enfoque
Otro enfoque posible utiliza un JsonConverter
personalizado para manejar la serialización de la clase PagedData
específicamente en lugar de usar el enfoque más general de "resolución + atributos" presentado anteriormente. El enfoque del convertidor requiere que haya otra propiedad en su clase PagedData
que especifique el nombre JSON que se usará para la propiedad de Data
enumerable. Puede pasar este nombre en el constructor PagedData
o configurarlo por separado, siempre que lo haga antes del tiempo de serialización. El convertidor buscará ese nombre y lo usará cuando escriba JSON para la propiedad enumerable.
Aquí está el código para el convertidor:
public class PagedDataConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(PagedData<>);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Type type = value.GetType();
var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
string dataPropertyName = (string)type.GetProperty("DataPropertyName", bindingFlags).GetValue(value);
if (string.IsNullOrEmpty(dataPropertyName))
{
dataPropertyName = "Data";
}
JObject jo = new JObject();
jo.Add(dataPropertyName, JArray.FromObject(type.GetProperty("Data").GetValue(value)));
foreach (PropertyInfo prop in type.GetProperties().Where(p => !p.Name.StartsWith("Data")))
{
jo.Add(prop.Name, new JValue(prop.GetValue(value)));
}
jo.WriteTo(writer);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Para usar este convertidor, primero agregue una propiedad de cadena llamada DataPropertyName
a su clase PagedData
(puede ser privada si lo desea), luego agregue un atributo [JsonConverter]
a la clase para vincularlo al convertidor:
[JsonConverter(typeof(PagedDataConverter))]
public class PagedData<T>
{
private string DataPropertyName { get; set; }
public IEnumerable<T> Data { get; private set; }
...
}
Y eso es. Siempre que haya establecido la propiedad DataPropertyName
, el convertidor la recogerá en la serialización.
Fiddle: https://dotnetfiddle.net/8E8fEE