c# - Conversión personalizada de objetos específicos en JSON.NET
(1)
Estoy usando JSON.NET para serializar algunos de mis objetos, y me gustaría saber si hay una manera simple de anular el convertidor json.net predeterminado solo para un objeto específico.
Actualmente tengo la siguiente clase:
public class ChannelContext : IDataContext
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<INewsItem> Items { get; set; }
}
JSON.NET actualmente serializa lo anterior como:
{
"Id": 2,
"Name": "name value",
"Items": [ item_data_here ]
}
¿Es posible que esa clase específica lo formatee de esta manera?
"Id_2":
{
"Name": "name value",
"Items": [ item data here ]
}
Soy un poco nuevo en JSON.NET .. Me preguntaba si lo anterior tiene algo que ver con escribir un convertidor personalizado. No pude encontrar ningún ejemplo concreto sobre cómo escribir uno. Si alguien puede indicarme una fuente específica, realmente lo apreciaré.
Necesito encontrar una solución que haga que esa clase específica siempre convierta la misma, porque el contexto anterior es parte de un contexto aún más grande que el convertidor por defecto de JSON.NET convierte muy bien.
Espero que mi pregunta sea lo suficientemente clara ...
ACTUALIZAR:
Descubrí cómo crear un nuevo convertidor personalizado (al crear una nueva clase que hereda de JsonConverter y anula sus métodos abstractos), anulo el método WriteJson de la siguiente manera:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
ChannelContext contextObj = value as ChannelContext;
writer.WriteStartObject();
writer.WritePropertyName("id_" + contextObj.Id);
writer.WriteStartObject();
writer.WritePropertyName("Name");
serializer.Serialize(writer, contextObj.Name);
writer.WritePropertyName("Items");
serializer.Serialize(writer, contextObj.Items);
writer.WriteEndObject();
writer.WriteEndObject();
}
Esto de hecho hace el trabajo con éxito, pero ... Estoy intrigado si hay una manera de serializar el resto de las propiedades del objeto reutilizando el JsonSerializer predeterminado (o convertidor para el caso) en lugar de manualmente "Escribir" el objeto usando el jsonwriter metodos
ACTUALIZACIÓN 2: Estoy tratando de obtener una solución más genérica y se me ocurrió lo siguiente:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
// Write associative array field name
writer.WritePropertyName(m_FieldNameResolver.ResolveFieldName(value));
// Remove this converter from serializer converters collection
serializer.Converters.Remove(this);
// Serialize the object data using the rest of the converters
serializer.Serialize(writer, value);
writer.WriteEndObject();
}
Esto funciona bien cuando se agrega el convertidor manualmente al serializador, así:
jsonSerializer.Converters.Add(new AssociativeArraysConverter<DefaultFieldNameResolver>());
jsonSerializer.Serialize(writer, channelContextObj);
Pero no funciona cuando se usa el atributo [JsonConverter ()] establecido en mi coverter personalizado sobre la clase ChannelContext debido a un bucle de referencia que se produce al ejecutar:
serializer.Serialize(writer, value)
Obviamente, esto se debe a que mi convertidor personalizado ahora se considera el convertidor predeterminado para la clase una vez que se configura con el atributo JsonConverter, por lo que obtengo un bucle inifinite. Lo único que se me ocurre para resolver este problema es heredar de una clase jsonconverter básica y llamar al método base.serialize () en su lugar ... Pero, ¿existe tal clase JsonConverter?
¡Muchas gracias!
Mikey
Si alguien está interesado en mi solución:
Al serializar ciertas colecciones, quería crear una matriz json asociativa en lugar de una matriz json estándar, por lo que mi desarrollador del lado del cliente colega puede llegar a esos campos de manera eficiente, utilizando su nombre (o la clave) en lugar de iterar a través de ellos.
considera lo siguiente:
public class ResponseContext
{
private List<ChannelContext> m_Channels;
public ResponseContext()
{
m_Channels = new List<ChannelContext>();
}
public HeaderContext Header { get; set; }
[JsonConverter(
typeof(AssociativeArraysConverter<ChannelContextFieldNameResolver>))]
public List<ChannelContext> Channels
{
get { return m_Channels; }
}
}
[JsonObject(MemberSerialization = MemberSerialization.OptOut)]
public class ChannelContext : IDataContext
{
[JsonIgnore]
public int Id { get; set; }
[JsonIgnore]
public string NominalId { get; set; }
public string Name { get; set; }
public IEnumerable<Item> Items { get; set; }
}
El contexto de respuesta contiene la respuesta completa que se escribe en el cliente, como puede ver, incluye una sección llamada "canales", y en lugar de enviar los contextos de canal en una matriz normal, me gustaría poder enviar en el siguiente manera:
"Channels"
{
"channelNominalId1":
{
"Name": "name value1"
"Items": [ item data here ]
},
"channelNominalId2":
{
"Name": "name value2"
"Items": [ item data here ]
}
}
Ya que quería usar lo anterior para otros contextos también, y podría decidir usar una propiedad diferente como su "clave", o incluso podría elegir crear mi propio nombre único, que no tiene que ver con ninguna propiedad, Necesitaba algún tipo de solución genérica, por lo tanto escribí una clase genérica llamada AssociativeArraysConverter, que se hereda de JsonConverter de la siguiente manera:
public class AssociativeArraysConverter<T> : JsonConverter
where T : IAssociateFieldNameResolver, new()
{
private T m_FieldNameResolver;
public AssociativeArraysConverter()
{
m_FieldNameResolver = new T();
}
public override bool CanConvert(Type objectType)
{
return typeof(IEnumerable).IsAssignableFrom(objectType) &&
!typeof(string).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
IEnumerable collectionObj = value as IEnumerable;
writer.WriteStartObject();
foreach (object currObj in collectionObj)
{
writer.WritePropertyName(m_FieldNameResolver.ResolveFieldName(currObj));
serializer.Serialize(writer, currObj);
}
writer.WriteEndObject();
}
}
Y declaró la siguiente interfaz:
public interface IAssociateFieldNameResolver
{
string ResolveFieldName(object i_Object);
}
Ahora todo lo que queda por hacer es crear una clase que implemente la única función de IAssociateFieldNameResolver, que acepta cada elemento de la colección, y devuelve una cadena basada en ese objeto, que actuará como la clave del objeto asociativo del elemento.
Ejemplo para tal clase es:
public class ChannelContextFieldNameResolver : IAssociateFieldNameResolver
{
public string ResolveFieldName(object i_Object)
{
return (i_Object as ChannelContext).NominalId;
}
}