property - JSON.Net-¿Cambiar el campo de tipo $ a otro nombre?
newtonsoft json serialize c# ignore property (8)
Al usar Json.Net, entiendo cómo obtener la propiedad $ type en el json representado, pero ¿hay alguna forma de cambiar el nombre de campo? Necesito usar "__type" en lugar de "$ type".
El uso de un convertidor personalizado debe hacer el trabajo.
public CustomConverter : JsonConverter
{
public override bool CanWrite => true;
public override bool CanRead => true;
public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
=> throw new NotImplementedException();
public override void WriteJson(JsonWriter writer,
object value,
JsonSerializer serializer)
{
var jOjbect = (JObject)JToken.FromObject(value);
jOjbect.Add(new JProperty("type", value.GetType().Name));
jOjbect.WriteTo(writer);
}
}
Hay otra opción que permite serializar el nombre de propiedad de tipo personalizado en Json.NET
. La idea es no escribir la propiedad de $type
predeterminada, sino introducir el nombre de tipo como propiedad de la clase en sí.
Supongamos que tenemos una clase de Location
:
public class Location
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
Primero, debemos introducir el nombre de propiedad de tipo y modificar la clase como se muestra a continuación:
public class Location
{
[JsonProperty("__type")]
public string EntityTypeName
{
get
{
var typeName = string.Format("{0}, {1}", GetType().FullName, GetType().Namespace);
return typeName;
}
}
public double Latitude { get; set; }
public double Longitude { get; set; }
}
Luego, configure JsonSerializerSettings.TypeNameHandling
en TypeNameHandling.None
para que el deserializador omita la representación del atributo predeterminado de $type
.
Eso es.
Ejemplo
var point = new Location() { Latitude = 51.5033630, Longitude = -0.1276250 };
var jsonLocation = JsonConvert.SerializeObject(point, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.None, //do not write type property(!)
});
Console.WriteLine(jsonLocation);
Resultado
{"__type":"Namespace.Location, Namespace","Latitude":51.503363,"Longitude":-0.127625}
Necesitábamos esto, así que creé un JsonReader personalizado. Estamos utilizando el descanso en nuestros servicios web de MS con modelos de datos complejos y es necesario que reemplacemos la propiedad "__type" con "$ type".
class MSJsonReader : JsonTextReader
{
public MSJsonTextReader(TextReader reader) : base(reader) { }
public override bool Read()
{
var hasToken = base.Read();
if (hasToken && base.TokenType == JsonToken.PropertyName && base.Value != null && base.Value.Equals("__type"))
base.SetToken(JsonToken.PropertyName, "$type");
return hasToken;
}
}
Así es como lo usamos.
using(JsonReader jr = new MSJsonTextReader(sr))
{
JsonSerializer s = new JsonSerializer();
s.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
s.NullValueHandling = NullValueHandling.Ignore;
s.TypeNameHandling = TypeNameHandling.Auto; // Important!
s.Binder = new MSRestToJsonDotNetSerializationBinder("Server.DataModelsNamespace", "Client.GeneratedModelsNamespace");
T deserialized = s.Deserialize<T>(jr);
return deserialized;
}
Aquí está nuestro MSRestToJsonDotNetSerializationBinder que completa la compatibilidad entre MS rest y Json.Net.
class MSRestToJsonDotNetSerializationBinder : System.Runtime.Serialization.SerializationBinder
{
public string ServiceNamespace { get; set; }
public string LocalNamespace { get; set; }
public MSRestToJsonDotNetSerializationBinder(string serviceNamespace, string localNamespace)
{
if (serviceNamespace.EndsWith("."))
serviceNamespace = serviceNamespace.Substring(0, -1);
if(localNamespace.EndsWith("."))
localNamespace = localNamespace.Substring(0, -1);
ServiceNamespace = serviceNamespace;
LocalNamespace = localNamespace;
}
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
assemblyName = null;
typeName = string.Format("{0}:#{1}", serializedType.Name, ServiceNamespace); // MS format
}
public override Type BindToType(string assemblyName, string typeName)
{
string jsonDotNetType = string.Format("{0}.{1}", LocalNamespace, typeName.Substring(0, typeName.IndexOf(":#")));
return Type.GetType(jsonDotNetType);
}
}
Parece que esto está codificado como public const string TypePropertyName = "$type";
en Newtonsoft.Json.Serialization.JsonTypeReflector
que Newtonsoft.Json.Serialization.JsonTypeReflector
es una clase estática interna.
Lo necesitaba yo mismo, y lo único que se me ocurre es tener la versión personalizada modificada de json.net. Que por supuesto es una gran pita.
También podrías hacerlo de esta manera:
[JsonConverter(typeof(JsonSubtypes), "ClassName")]
public class Annimal
{
public virtual string ClassName { get; }
public string Color { get; set; }
}
Necesitará el convertidor JsonSubtypes
que no forma parte del proyecto Newtonsoft.Json
.
Tuve que hacer esto para mi API REST de IU, ya que Angular.js disregards nombres de los campos que comienzan con un signo de dólar ($).
Así que aquí hay una solución que cambia el $type
a __type
para toda la API web y funciona tanto para la serialización como para la deserialización.
Para poder utilizar un JsonWriter
personalizado y un JsonReader
personalizado (como se propone en las otras respuestas a esta pregunta), tenemos que heredar el JsonMediaTypeFormatter
y anular los métodos correspondientes:
internal class CustomJsonNetFormatter : JsonMediaTypeFormatter
{
public override JsonReader CreateJsonReader(Type type, Stream readStream, Encoding effectiveEncoding)
{
return new CustomJsonReader(readStream, effectiveEncoding);
}
public override JsonWriter CreateJsonWriter(Type type, Stream writeStream, Encoding effectiveEncoding)
{
return new CustomJsonWriter(writeStream, effectiveEncoding);
}
private class CustomJsonWriter : JsonTextWriter
{
public CustomJsonWriter(Stream writeStream, Encoding effectiveEncoding)
: base(new StreamWriter(writeStream, effectiveEncoding))
{
}
public override void WritePropertyName(string name, bool escape)
{
if (name == "$type") name = "__type";
base.WritePropertyName(name, escape);
}
}
private class CustomJsonReader : JsonTextReader
{
public CustomJsonReader(Stream readStream, Encoding effectiveEncoding)
: base(new StreamReader(readStream, effectiveEncoding))
{
}
public override bool Read()
{
var hasToken = base.Read();
if (hasToken && TokenType == JsonToken.PropertyName && Value != null && Value.Equals("__type"))
{
SetToken(JsonToken.PropertyName, "$type");
}
return hasToken;
}
}
}
Por supuesto, debe registrar el formateador personalizado en su WebApiConfig
. Así que reemplazamos el formateador Json.NET predeterminado por nuestro personalizado:
config.Formatters.Remove(config.Formatters.JsonFormatter);
config.Formatters.Add(new CustomJsonNetFormatter());
Hecho.
al serializar, hay una buena forma de anular el nombre de la propiedad:
public class CustomJsonWriter : JsonTextWriter
{
public CustomJsonWriter(TextWriter writer) : base(writer)
{
}
public override void WritePropertyName(string name, bool escape)
{
if (name == "$type") name = "__type";
base.WritePropertyName(name, escape);
}
}
var serializer = new JsonSerializer();
var writer = new StreamWriter(stream) { AutoFlush = true };
serializer.Serialize(new CustomJsonWriter(writer), objectToSerialize);
No he intentado la deserialización todavía, pero en el peor de los casos podría usar:
json.Replace("/"__type": /"", "/"type/": /"$type/");
http://json.codeplex.com/workitem/22429
"Preferiría mantener $ tipo duro codificado y consistente".
De acuerdo con lo que me pregunto?
http://json.codeplex.com/workitem/21989
Preferiría que no, creo que esto es demasiado específico para mí y no quiero exagerar con la configuración. En algún momento, es probable que implementemos esto, http://json.codeplex.com/workitem/21856 , que permita a las personas leer / escribir sus propias propiedades meta en el JSON y podría volver a implementar el manejo del nombre de tipo con un nuevo nombre de propiedad. La otra opción es simplemente modificar el código fuente para que usted tenga ese nombre de propiedad.
Esta es mi solución ...
json.Replace("/"$type/": /"", "/"type/": /"");