que - instanciar una clase c#
¿Puedo especificar una ruta en un atributo para asignar una propiedad en mi clase a una propiedad secundaria en mi JSON? (4)
Bueno, si solo necesita una propiedad adicional única, un enfoque simple es analizar su JSON a un
JObject
, use
ToObject()
para completar su clase desde
JObject
y luego use
SelectToken()
para extraer la propiedad adicional.
Entonces, suponiendo que su clase se vea así:
class Person
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("age")]
public string Age { get; set; }
public string ProfilePicture { get; set; }
}
Podrías hacer esto:
string json = @"
{
""name"" : ""Joe Shmoe"",
""age"" : 26,
""picture"":
{
""id"": 123456,
""data"":
{
""type"": ""jpg"",
""url"": ""http://www.someplace.com/mypicture.jpg""
}
}
}";
JObject jo = JObject.Parse(json);
Person p = jo.ToObject<Person>();
p.ProfilePicture = (string)jo.SelectToken("picture.data.url");
Violín: https://dotnetfiddle.net/7gnJCK
Si prefiere una solución más elegante, puede crear un
JsonConverter
personalizado para permitir que el atributo
JsonProperty
comporte como usted describe.
El convertidor necesitaría operar a nivel de clase y usar un poco de reflexión combinada con la técnica anterior para completar todas las propiedades.
Así es como se vería en el código:
class JsonPathConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
object targetObj = Activator.CreateInstance(objectType);
foreach (PropertyInfo prop in objectType.GetProperties()
.Where(p => p.CanRead && p.CanWrite))
{
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = (att != null ? att.PropertyName : prop.Name);
JToken token = jo.SelectToken(jsonPath);
if (token != null && token.Type != JTokenType.Null)
{
object value = token.ToObject(prop.PropertyType, serializer);
prop.SetValue(targetObj, value, null);
}
}
return targetObj;
}
public override bool CanConvert(Type objectType)
{
// CanConvert is not called when [JsonConverter] attribute is used
return false;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Para demostrarlo, supongamos que el JSON ahora se ve así:
{
"name": "Joe Shmoe",
"age": 26,
"picture": {
"id": 123456,
"data": {
"type": "jpg",
"url": "http://www.someplace.com/mypicture.jpg"
}
},
"favorites": {
"movie": {
"title": "The Godfather",
"starring": "Marlon Brando",
"year": 1972
},
"color": "purple"
}
}
... y está interesado en la película favorita de la persona (título y año) y el color favorito, además de la información de antes.
Primero marcaría su clase de destino con un atributo
[JsonConverter]
para asociarla con el convertidor personalizado, luego usaría los atributos
[JsonProperty]
en cada propiedad, especificando la ruta de propiedad deseada (
[JsonProperty]
entre mayúsculas y minúsculas) como el nombre.
Las propiedades de destino tampoco tienen que ser primitivas: puede usar una clase secundaria como hice aquí con
Movie
(y observe que no se requiere una clase de
Favorites
).
[JsonConverter(typeof(JsonPathConverter))]
class Person
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("age")]
public int Age { get; set; }
[JsonProperty("picture.data.url")]
public string ProfilePicture { get; set; }
[JsonProperty("favorites.movie")]
public Movie FavoriteMovie { get; set; }
[JsonProperty("favorites.color")]
public string FavoriteColor { get; set; }
}
// Don''t need to mark up these properties because they are covered by the
// property paths in the Person class
class Movie
{
public string Title { get; set; }
public int Year { get; set; }
}
Con todos los atributos en su lugar, puede simplemente deserializarse de manera normal y debería "funcionar":
Person p = JsonConvert.DeserializeObject<Person>(json);
Violín: https://dotnetfiddle.net/Ljw32O
Hay un código (que no puedo cambiar) que usa DeserializeObject
DeserializeObject<T>(strJSONData)
para tomar datos de una solicitud web y convertirlos en un objeto de clase (puedo cambiar la clase).
Al decorar las propiedades de mi clase con
[DataMember(Name = "raw_property_name")]
puedo asignar los datos JSON sin procesar a la propiedad correcta en mi clase.
¿Hay alguna manera de asignar la propiedad secundaria de un objeto complejo JSON a una propiedad simple?
Aquí hay un ejemplo:
{
"picture":
{
"id": 123456,
"data":
{
"type": "jpg",
"url": "http://www.someplace.com/mypicture.jpg"
}
}
}
No me importa nada del resto del objeto de imagen, excepto la URL, por lo que no quiero configurar un objeto complejo en mi clase C #. Realmente solo quiero algo como:
[DataMember(Name = "picture.data.url")]
public string ProfilePicture { get; set; }
es posible?
En lugar de hacer
lastLevel [nesting [i]] = new JValue(prop.GetValue (value));
Tu tienes que hacer
lastLevel[nesting[i]] = JValue.FromObject(jValue);
De lo contrario tenemos un
No se pudo determinar el tipo de objeto JSON para el tipo ...
excepción
Un código completo sería este:
object jValue = prop.GetValue(value);
if (prop.PropertyType.IsArray)
{
if(jValue != null)
//https://.com/a/20769644/249895
lastLevel[nesting[i]] = JArray.FromObject(jValue);
}
else
{
if (prop.PropertyType.IsClass && prop.PropertyType != typeof(System.String))
{
if (jValue != null)
lastLevel[nesting[i]] = JValue.FromObject(jValue);
}
else
{
lastLevel[nesting[i]] = new JValue(jValue);
}
}
La respuesta marcada no está 100% completa ya que ignora cualquier IContractResolver que pueda registrarse, como CamelCasePropertyNamesContractResolver, etc.
También devolver false para can convert evitará otros casos de usuario, así que lo cambié para
return objectType.GetCustomAttributes(true).OfType<JsonPathConverter>().Any();
Aquí está la versión actualizada: https://dotnetfiddle.net/F8C8U8
También
JsonProperty
la necesidad de establecer una
JsonProperty
en una propiedad como se ilustra en el enlace.
Si por alguna razón el enlace de arriba muere o explota, también incluyo el siguiente código:
public class JsonPathConverter : JsonConverter
{
/// <inheritdoc />
public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
object targetObj = Activator.CreateInstance(objectType);
foreach (PropertyInfo prop in objectType.GetProperties().Where(p => p.CanRead && p.CanWrite))
{
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = att != null ? att.PropertyName : prop.Name;
if (serializer.ContractResolver is DefaultContractResolver)
{
var resolver = (DefaultContractResolver)serializer.ContractResolver;
jsonPath = resolver.GetResolvedPropertyName(jsonPath);
}
if (!Regex.IsMatch(jsonPath, @"^[a-zA-Z0-9_.-]+$"))
{
throw new InvalidOperationException($"JProperties of JsonPathConverter can have only letters, numbers, underscores, hiffens and dots but name was ${jsonPath}."); // Array operations not permitted
}
JToken token = jo.SelectToken(jsonPath);
if (token != null && token.Type != JTokenType.Null)
{
object value = token.ToObject(prop.PropertyType, serializer);
prop.SetValue(targetObj, value, null);
}
}
return targetObj;
}
/// <inheritdoc />
public override bool CanConvert(Type objectType)
{
// CanConvert is not called when [JsonConverter] attribute is used
return objectType.GetCustomAttributes(true).OfType<JsonPathConverter>().Any();
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var properties = value.GetType().GetRuntimeProperties().Where(p => p.CanRead && p.CanWrite);
JObject main = new JObject();
foreach (PropertyInfo prop in properties)
{
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = att != null ? att.PropertyName : prop.Name;
if (serializer.ContractResolver is DefaultContractResolver)
{
var resolver = (DefaultContractResolver)serializer.ContractResolver;
jsonPath = resolver.GetResolvedPropertyName(jsonPath);
}
var nesting = jsonPath.Split(''.'');
JObject lastLevel = main;
for (int i = 0; i < nesting.Length; i++)
{
if (i == nesting.Length - 1)
{
lastLevel[nesting[i]] = new JValue(prop.GetValue(value));
}
else
{
if (lastLevel[nesting[i]] == null)
{
lastLevel[nesting[i]] = new JObject();
}
lastLevel = (JObject)lastLevel[nesting[i]];
}
}
}
serializer.Serialize(writer, main);
}
}
Si alguien necesita usar el JsonPathConverter de @BrianRogers también con la opción
WriteJson
, aquí hay una solución (que solo funciona para rutas con
puntos solamente
):
Elimine la propiedad
CanWrite
para que vuelva a ser
true
por defecto.
Reemplace el código
WriteJson
por lo siguiente:
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
var properties = value.GetType().GetRuntimeProperties ().Where(p => p.CanRead && p.CanWrite);
JObject main = new JObject ();
foreach (PropertyInfo prop in properties) {
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = (att != null ? att.PropertyName : prop.Name);
var nesting=jsonPath.Split(new[] { ''.'' });
JObject lastLevel = main;
for (int i = 0; i < nesting.Length; i++) {
if (i == nesting.Length - 1) {
lastLevel [nesting [i]] = new JValue(prop.GetValue (value));
} else {
if (lastLevel [nesting [i]] == null) {
lastLevel [nesting [i]] = new JObject ();
}
lastLevel = (JObject)lastLevel [nesting [i]];
}
}
}
serializer.Serialize (writer, main);
}
Como dije anteriormente, esto solo funciona para rutas que contienen
puntos
.
Dado eso, debe agregar el siguiente código a
ReadJson
para evitar otros casos:
[...]
string jsonPath = (att != null ? att.PropertyName : prop.Name);
if (!Regex.IsMatch(jsonPath, @"^[a-zA-Z0-9_.-]+$")) {
throw new InvalidOperationException("JProperties of JsonPathConverter can have only letters, numbers, underscores, hiffens and dots."); //Array operations not permitted
}
JToken token = jo.SelectToken(jsonPath);
[...]