Come omettere / ignorare / saltare i letterali degli oggetti vuoti nel JSON prodotto?

Sto usando Json.NET per convertire un complesso object grafico C# in JSON. A causa dell’ignoranza delle proprietà che hanno valori predefiniti nell’object, di solito ottengo i letterali degli oggetti vuoti nell’output, cosa che vorrei omettere.

Per esempio:

 public class Sample { public int Value { get; set; } public string Name { get; set; } } public class ParentSample { // this property should never be null, hence the initializer public Sample Sample { get; } = new Sample(); } .. var obj = new ParentSample(); // settings for indentation and excluding default values omitted for clarity var output = JsonConvert.SerializeObject(obj, ... ); // output will be // { // Sample: {} // } // // I'd like it to be // {} 

Sono a conoscenza di alcune soluzioni specifiche del tipo, ad esempio l’aggiunta di un metodo booleano ParentSample tipo ParentSample e la verifica di tutte le proprietà predefinite. Tuttavia mi piacerebbe una soluzione generale sotto forma di un risolutore di contratto personalizzato, ad esempio.

Nei commenti sembra che tu abbia deciso di ricorrere all’utilizzo di Regex per sbarazzarti degli oggetti vuoti. Un problema con questa idea è che probabilmente non gestirà la situazione in cui si hanno quelli che chiamerò “oggetti vuoti ricorsivi”. In altre parole qualcosa del genere:

 { "foo": { "bar": {}, "baz": {} } } 

Se riesci a rimuovere la bar oggetti vuoti di livello più profondo e baz con Regex (e allo stesso tempo ti rendi conto che è necessario rimuovere la virgola tra di essi per mantenere valido il JSON), rimarrà ancora un object vuoto: foo .

 { "foo": { } } 

Penso che una soluzione migliore sia caricare i tuoi dati in una gerarchia di JToken e quindi utilizzare un metodo ricorsivo per rimuovere tutti i bambini vuoti prima di scriverlo su JSON. Qualcosa di simile dovrebbe funzionare per le tue esigenze:

 using System; using Newtonsoft.Json.Linq; public static class JsonHelper { public static string SerializeToMinimalJson(object obj) { return JToken.FromObject(obj).RemoveEmptyChildren().ToString(); } public static JToken RemoveEmptyChildren(this JToken token) { if (token.Type == JTokenType.Object) { JObject copy = new JObject(); foreach (JProperty prop in token.Children()) { JToken child = prop.Value; if (child.HasValues) { child = child.RemoveEmptyChildren(); } if (!child.IsEmptyOrDefault()) { copy.Add(prop.Name, child); } } return copy; } else if (token.Type == JTokenType.Array) { JArray copy = new JArray(); foreach (JToken item in token.Children()) { JToken child = item; if (child.HasValues) { child = child.RemoveEmptyChildren(); } if (!child.IsEmptyOrDefault()) { copy.Add(child); } } return copy; } return token; } public static bool IsEmptyOrDefault(this JToken token) { return (token.Type == JTokenType.Array && !token.HasValues) || (token.Type == JTokenType.Object && !token.HasValues) || (token.Type == JTokenType.String && token.ToString() == String.Empty) || (token.Type == JTokenType.Boolean && token.Value() == false) || (token.Type == JTokenType.Integer && token.Value() == 0) || (token.Type == JTokenType.Float && token.Value() == 0.0) || (token.Type == JTokenType.Null); } } 

Puoi quindi serializzare il tuo object (i) in questo modo:

 var json = JsonHelper.SerializeToMinimalJson(obj); 

Fiddle: https://dotnetfiddle.net/awRPMR

MODIFICARE

Se si desidera onorare l’attributo [DefaultValue] con questo metodo, è ansible farlo modificando il metodo SerializeToMinimalJson() per creare un’istanza di JsonSerializer , impostando la proprietà DefaultValueHandling su di esso e quindi passandola a JToken.FromObject() come mostrato di seguito. (Deve essere fatto in questo modo perché i JTokens non hanno riferimenti agli oggetti originali da cui sono stati creati usando FromObject() , quindi non c’è modo di ottenere i valori degli attributi [DefaultValue] successivamente.)

 public static string SerializeToMinimalJson(object obj) { var serializer = new JsonSerializer(); serializer.NullValueHandling = NullValueHandling.Ignore; serializer.DefaultValueHandling = DefaultValueHandling.Ignore; return JToken.FromObject(obj, serializer).RemoveEmptyChildren().ToString(); } 

Se lo fai, potresti anche voler cambiare il metodo IsEmptyOrDefault() modo che non rimuova i valori che sono il “default predefinito”. Puoi ridurlo a questo:

 public static bool IsEmptyOrDefault(this JToken token) { return (token.Type == JTokenType.Array && !token.HasValues) || (token.Type == JTokenType.Object && !token.HasValues); } 

Fiddle: https://dotnetfiddle.net/0yVRI5

Puoi dare un JsonSerializerSettings al metodo usando NullValueHandling.Ignore :

 var output = JsonConvert.SerializeObject(obj, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }); 

Se queste impostazioni non danno quello che ti serve, controlla: la documentazione. Lì puoi trovare tutte le proprietà e una descrizione.

Modifica: utilizzando il figlio (esempio) come struct funziona con DefaultValueHandling.Ignore. Ma @ Zoltán Tamási userà un’espressione regolare a causa della complessità della class.

Ho implementato una soluzione leggermente diversa che utilizza un metodo generico, una riflessione e alcune funzionalità predefinite di Newtonsoft.Json ShouldSerialize. Non elegante ma concettualmente semplice per la mia particolare esigenza. Di seguito è riportato lo snippet di codice LinqPad.

 void Main() { Person person = new Person(); person.MyAddress = new Address(); var ret = person.ShouldSerializeMyAddress(); var json = JsonConvert.SerializeObject(person, Newtonsoft.Json.Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); json.Dump(); } public static class JsonExtensions { public static bool ShouldSerialize(this object self) { if (self == null) return false; var methods = self.GetType().GetMethods().Where(p => p.Name.StartsWith("ShouldSerialize")); return methods.Any(p => p.Invoke(self, null) is bool value && value); } } public class Person { public Address MyAddress { get; set; } public bool ShouldSerializeMyAddress() { return MyAddress.ShouldSerialize(); } } public class Address { public string Street { get; set; } public bool ShouldSerializeStreet() { return false; // or whatever your property serialization criteria should be } public string City { get; set; } public bool ShouldSerializeCity() { return false; } public string State { get; set; } public bool ShouldSerializeState() { return false; } public string Zip { get; set; } public bool ShouldSerializeZip() { return false; } }