Can I optionally turn off the JsonIgnore attribute at runtime? - c#

I am creating a JSON file with Newtonsoft.Json from a set of classes. The file created is very large, so I have created JsonProperty's for the properties to reduce the size and added JsonIgnore and custom formatting for some datatypes.
The result is a reduction from 24MB to 1MB, which is great; however, I'd like the option to produce either the full version or the reduced property version at runtime.
Is there anyway I can get the serializer to optionally use the attributes?

Yes, this can be done using a custom ContractResolver.
You didn't show any code, so I'll just make up an example. Let's say I have a class Foo as shown below. I want the Id and Name properties in the serialization output, but I'm definitely not interested in the AlternateName and Color. I've marked those with [JsonIgnore]. I want the description to appear, but sometimes this can get really long, so I've used a custom JsonConverter to limit its length. I also want to use a shorter property name for the description, so I've marked it with [JsonProperty("Desc")].
class Foo
{
public int Id { get; set; }
public string Name { get; set; }
[JsonIgnore]
public string AlternateName { get; set; }
[JsonProperty("Desc")]
[JsonConverter(typeof(StringTruncatingConverter))]
public string Description { get; set; }
[JsonIgnore]
public string Color { get; set; }
}
When I serialize an instance of the above...
Foo foo = new Foo
{
Id = 1,
Name = "Thing 1",
AlternateName = "The First Thing",
Description = "This is some lengthy text describing Thing 1 which you'll no doubt find very interesting and useful.",
Color = "Yellow"
};
string json = JsonConvert.SerializeObject(foo, Formatting.Indented);
...I get this output:
{
"Id": 1,
"Name": "Thing 1",
"Desc": "This is some lengthy text describing Thing 1 "
}
Now, let's say that I sometimes want to get the full JSON output, ignoring my customizations. I can use a custom ContractResolver to programmatically "unapply" the attributes from the class. Here's the code for the resolver:
class IgnoreJsonAttributesResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
foreach (var prop in props)
{
prop.Ignored = false; // Ignore [JsonIgnore]
prop.Converter = null; // Ignore [JsonConverter]
prop.PropertyName = prop.UnderlyingName; // restore original property name
}
return props;
}
}
To use the resolver, I add it to the JsonSerializerSettings and pass the settings to the serializer like this:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new IgnoreJsonAttributesResolver();
settings.Formatting = Formatting.Indented;
string json = JsonConvert.SerializeObject(foo, settings);
The output now includes the ignored properties, and the description is no longer truncated:
{
"Id": 1,
"Name": "Thing 1",
"AlternateName": "The First Thing",
"Description": "This is some lengthy text describing Thing 1 which you'll no doubt find very interesting and useful.",
"Color": "Yellow"
}
Full demo here: https://dotnetfiddle.net/WZpeWt

Json support us to ignore property that don't want return.
Example
class Foo
{
public int Id { get; set; }
public string Name { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string AlternateName { get; set; }
}
How to use it:
Foo foo = new Foo
{
Id = 1,
Name = "Thing 1",
AlternateName = null,
};
string json = JsonConvert.SerializeObject(foo);

If you are willing to use F# (or simply use an API not optimized for C#), the FSharp.JsonSkippable library contains a generic wrapper type that allows you to control in a simple and strongly typed manner whether to include a given property when serializing (and determine whether a property was included when deserializing), and moreover, to control/determine exclusion separately of nullability. (Full disclosure: I'm the author of the library.)

Related

C# JSON.NET How to ignore a property in deserialization but not in serialization

I have an object I am serializing into JSON then deserializing back.
The structure of one of the properties has changed and now deserializing crashes, so I need to ignore deserializing that property for now.
I can ignore the property completely with [JsonIgnore, JsonProperty(Required = Required.Default)], but that also ignores the property from serialization - which needs to stay so no data is lost, even if it isn't' being serialized at this moment.
There is an answer here, although it's a bit old: https://stackoverflow.com/a/31732029/12431728 However, it still seems viable to me, I'm not aware of a better / different way to do it. That answer suggests marking the real property with JsonIgnore and creating a "get-only proxy property."
Then it goes on to suggest creating a custom ContractResolver if you need this functionality for many properties (AKA reusable solution).
You could use a JsonContractResolver to set the JsonProperty.ShouldDeserialize property as seen in one of the test suites of Newtonsoft.Json.
For Example,
public class ShouldDeserializeContractResolver : DefaultContractResolver
{
public static new readonly ShouldDeserializeContractResolver Instance = new ShouldDeserializeContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
MethodInfo shouldDeserializeMethodInfo = member.DeclaringType.GetMethod("ShouldDeserialize" + member.Name);
if (shouldDeserializeMethodInfo != null)
{
property.ShouldDeserialize = o => { return (bool)shouldDeserializeMethodInfo.Invoke(o, null); };
}
return property;
}
}
Example Code
var instance = new RootObject { ID = 2, DisplayName = "John Doe" };
var json = JsonConvert.SerializeObject(instance);
var settings = new JsonSerializerSettings
{
ContractResolver = ShouldDeserializeContractResolver.Instance
};
Console.WriteLine(json);
var deserializedInstance = JsonConvert.DeserializeObject<RootObject>(json, settings);
Console.WriteLine($"Deserialized => Id={deserializedInstance.ID}, Name={deserializedInstance.DisplayName} ");
Where RootObject is defined as
public class RootObject
{
public int ID { get; set; }
public string DisplayName { get; set; }
public bool ShouldDeserializeDisplayName() => false;
}
Output
{"ID":2,"DisplayName":"John Doe"} //During Serialization
Deserialized => Id=2, Name= // During Deserialization
You could serialise and deserialise from a JObject as in the link from Amadeu Antunes, although this solution is rather inelegant, as suddenly you've thrown loose typing into the mix.
Another potentially easier possibility is that if you have access to all of the json objects that you are deserialising from, you could do a bulk update in notepad++, ssms or whatever and just add in some default value into the json files.

How to mark certain property as required in json content

I am writing an application where I do need to handle some scenarios beforehand in my controller class like certain property must have been provided otherwise status code would be BadRequest. Here is my class lookalike.
public class MyClass
{
[Required]
public IEnumerable<NewObject> NewObjects { get; set; }
}
public class NewObject : INewObject
{
public NewObject(string typeName, IEnumerable<Property> properties)
{
TypeName = typeName;
Properties = properties;
}
[JsonProperty(Required = Required.Always)]
public string TypeName { get; }
public IEnumerable<IProperty> Properties { get; }
}
public interface IProperty
{
string Name { get; }
object Value { get; }
}
Now though I have marked TypeName as required property and if I do not pass that in json content while sending request from swagger, json deserialization doesn't fail. I tried to search but I got an answer that setting Required to Always should work.
Below is the Json Content I am passing through swagger:
{
"NewObjects": [
{
"Properties": [
{
"Name": "string",
"Value": ''
}
]
}
]
}
I wrote below piece of code too by looking at one of the solution:
var config = new HttpConfiguration();
var jsonFormatter = config.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
config.MapHttpAttributeRoutes();
Still it's not working:
Note: I am using Newtonsoft.Json version 11.0.1
This seems to be swagger issue because when I serialize input C# object and when again deserialize it, I am getting proper error.
For example in my controller class if I say:
var input2 = JsonConvert.DeserializeObject<MyClass>(JsonConvert.SerializeObject(input))
Then input2 throws an exception.
You can take a look at FluentValidation. If I am not mistaken it is designed to validate data in jsons forms specifically.
using FluentValidation;
public CertainActionValidator()
{
RuleFor(x => x.PropertyName).NotEmpty()
}
You can add plenty of additional conditions in there.

JsonIgnore attribute conditional analog but not ShouldSerialize

I want to implement a conditional version of [JsonIgnore] attribute in C#. I don't want to use ShouldSerializePropertyName due to it's dependency on hardcoded property name.
My API model inherits from the database model and I certainly want to ignore database Ids but I also want to ignore some other properties based on which features the customer payed for. I don't want to use ShouldSerialize technique because the other developers in my team could change the database property name and accidentally make visible what should not be visible.
I read if I can optionally turn off the JsonIgnore attribute at runtime
but it looks like the suggested technique turns off all JsonIgnore together. What I want to do is to have just some of them off based on some condition.
Is there a work around? Is there some attribute which could do that? If I need to write a custom attribute, could you show me how please? Thanks!
This is an interesting problem. My answer borrows heavily from the link you provided, but checks for a custom attribute defining your "Premium Content" (things that the user paid for):
Like your link, I have defined a class Foo, which will be serialized. It contains a child object PremiumStuff, which contains things that should only be serialized if the user paid for them. I have marked this child object with a custom attribute PremiumContent, which is also defined in this code snippet. I then used a custom class that inherits from DefaultContractResolver just like the link did, but in this implementation, I am checking each property for our custom attribute, and running the code in the if block only if the property is marked as PremiumContent. This conditional code checks a static bool called AllowPremiumContent to see whether we are allowing the premium content to be serialized. If it is not allowed, then we are setting the Ignore flag to true:
class Foo
{
public int Id { get; set; }
public string Name { get; set; }
[JsonIgnore]
public string AlternateName { get; set; }
[PremiumContent]
public PremiumStuff ExtraContent { get; set; }
}
class PremiumStuff
{
public string ExtraInfo { get; set; }
public string SecretInfo { get; set; }
}
class IncludePremiumContentAttributesResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
foreach (var prop in props)
{
if (Attribute.IsDefined(type.GetProperty(prop.PropertyName), typeof(PremiumContent)))
{
//if the attribute is marked with [PremiumContent]
if (PremiumContentRights.AllowPremiumContent == false)
{
prop.Ignored = true; // Ignore this if PremiumContentRights.AllowPremiumContent is set to false
}
}
}
return props;
}
}
[System.AttributeUsage(System.AttributeTargets.All)]
public class PremiumContent : Attribute
{
}
public static class PremiumContentRights
{
public static bool AllowPremiumContent = true;
}
Now, let's implement this and see what we get. Here is my test code:
PremiumContentRights.AllowPremiumContent = true;
Foo foo = new Foo()
{
Id = 1,
Name = "Hello",
AlternateName = "World",
ExtraContent = new PremiumStuff()
{
ExtraInfo = "For premium",
SecretInfo = "users only."
}
};
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new IncludePremiumContentAttributesResolver();
settings.Formatting = Formatting.Indented;
string json = JsonConvert.SerializeObject(foo, settings);
Debug.WriteLine(json);
In the first line, PremiumContentRights.AllowPremiumContent is set to true, and here is the output:
{
"Id": 1,
"Name": "Hello",
"ExtraContent": {
"ExtraInfo": "For premium",
"SecretInfo": "users only."
}
}
If we set AllowPremiumContent to False and run the code again, here is the output:
{
"Id": 1,
"Name": "Hello"
}
As you can see, the ExtraContent property is included or ignored depending on the state of the AllowPremiumContent variable.

Json Deserialised don't override the default values in the property

public class Student
{
public string Name { get; set;} = "ABCD";
public List<Subject> Subjects {get; set;}
public Student()
{
Subjects = new List<Subject>();
SetDefaultSubject();
}
private void SetDefaultSubject()
{
this.Subjects.Add(new Subject(){Name = "English"});
this.Subjects.Add(new Subject(){Name = "Maths"});
}
}
public class Subject
{
public string Name {get; set;}
}
I have a Json String which look like this
var jsonStudentData = #"{""Name"":""ABC"",""Subjects"":[{""Name"":""English""},{""Name"":""Maths""},{""Name"":""Hindi""},{""Name"":""Social Studies""}]}";
This is my code where i am deserialising jsonStudentData
JsonConvert.DeserializeObject<Student>(jsonStudentData);
I am getting the Output
Name = ABC,
Subject [English,Maths,English,Maths,Hindi,Social Studies]
but i want Output
Name = ABC,
Subject [English,Maths,Hindi,Social Studies]
Am i Doing Wrong anything here.
Well, your expectations are incorrect. Basically, the JSON deserializer is - entirely reasonably, IMO - executing code equivalent to:
var student = new Student
{
Name = "ABC",
Subjects =
{
// Each line here will just call Add
new Subject { Name = "English" },
new Subject { Name = "Maths" },
new Subject { Name = "Hindi" },
new Subject { Name = "Social Studies" },
}
};
That's what your JSON basically says it should do. It's your SetDefaultSubjects which is adding more information, regardless of what the JSON says.
I would personally suggest you remove the call to SetDefaultSubjects from your constructor - perhaps add a factory method of CreateStudentWithDefaultSubjects.
You might also consider making your List a set of some kind (e.g. HashSet) and make Subject implement IEquatable<Subject>, so that you can add duplicates and they'll be ignored.
Alternatively, you can use C# function to deserialize.
Assembly: System.Web.Extensions (in System.Web.Extensions.dll)
string jsonStudentData = #"{""Name"":""ABC"",""Subjects"":[{""Name"":""English""},{""Name"":""Maths""},{""Name"":""Hindi""},{""Name"":""Social Studies""}]}";
JavaScriptSerializer serializer = new JavaScriptSerializer();
Student newStudent = serializer.Deserialize<Student>(jsonStudentData);

Newtonsoft JsonSerializer - Lower case properties and dictionary [duplicate]

This question already has answers here:
Keep casing when serializing dictionaries
(4 answers)
Closed 7 years ago.
I'm using json.net (Newtonsoft's JsonSerializer). I need to customize serialization in order to meet following requirements:
property names must start with lower case letter.
Dictionary must be serialized into jsonp where keys will be used for property names. LowerCase rule does not apply for dictionary keys.
for example:
var product = new Product();
procuct.Name = "Product1";
product.Items = new Dictionary<string, Item>();
product.Items.Add("Item1", new Item { Description="Lorem Ipsum" });
must serialize into:
{
name: "Product1",
items : {
"Item1": {
description : "Lorem Ipsum"
}
}
}
notice that property Name serializes into "name", but key Item1 serializes into "Item1";
I have tried to create CustomJsonWriter to serialize property names, but it changes also dicionary keys.
public class CustomJsonWriter : JsonTextWriter
{
public CustomJsonWriter(TextWriter writer) : base(writer)
{
}
public override void WritePropertyName(string name, bool escape)
{
if (name != "$type")
{
name = name.ToCamelCase();
}
base.WritePropertyName(name, escape);
}
}
You could try using the CamelCasePropertyNamesContractResolver.
var serializerSettings = new JsonSerializerSettings();
serializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
var json = JsonConvert.SerializeObject(product, serializerSettings);
I'm just not sure how it'll handle the dictionary keys and I don't have time right this second to try it. If it doesn't handle the keys correctly it's still worth keeping in mind for the future rather than writing your own custom JSON writer.
You can use a JsonProperty to change how something is serialized/deserialized. When you define your object add the property items to the fields you would like represented differently in the JSON. This only works with NewtonsoftJSON. Other libraries may do it differently.
public class Product
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("items")]
public Dictionary<string, Item> Items { get; set; }
}
public class Item
{
[JsonProperty("description")]
public string Description { get; set; }
}

Categories

Resources