In the Environment property of Audit.NET's output none of the properties (DomainName, MachineName, etc) are really useful to us and take a lot of extra space in our audit database.
To remove them I've found I can either do this:
auditScope.Event.Environment = new AuditEventEnvironment()
...or I can put a custom ContractResolver on Audit.Core.Configuration.JsonSettings.ContractResolver that skips the Environment property.
But is there a better way to disable the Environment property?
Of the options mentioned in the question, this is the only one that completely removes Environment from the serialised audit entries. The other option serialises to an empty Environment object (and if Environment is set to null then you get a NullReferenceException).
In ConfigureServices set the ContractResolver to EnvironmentSkippingContractResolver:
Audit.Core.Configuration.JsonSettings.ContractResolver = EnvironmentSkippingContractResolver.Instance;
EnvironmentSkippingContractResolver looks like this:
public class EnvironmentSkippingContractResolver : DefaultContractResolver
{
public static readonly EnvironmentSkippingContractResolver Instance = new EnvironmentSkippingContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = instance => member.Name != "Environment";
return property;
}
}
Related
tl;dr; In Newtonsoft JSON.NET, how do you ignore default values for some types (enums), and not others (ints)?
My team is consuming a library that uses protocol buffers for their business entities. Every enumeration in this library/protobuf has a default value of 0, "ValueNotSet". My team is using Newtonsoft JSON.NET to serialize these entities. Here's a diluted example for a bakery's inventory:
public enum Flavor { ValueNotSet, Cherry, Blueberry, Cheese };
public class DanishInventory { public int QtyInStock; public Flavor; }
In order to conserve resources we do not want to serialize ValueNotSet (the real world scenario has many enumerations), but having zero cheese danishes in stock is valid and we do want to serialize zero. Because of this, we cannot use DefaultValueHandling = Ignore in settings.
I created a custom JsonConverter, but by the time WriteJson(...) is called the key is already in the JsonWriter. So if I write nothing the JSON is invalid, and I don't see an obvious method to back-track the writer to overwrite a key. So what is the best way to ignore default values for some types (e.g. enums), but not others (e.g. ints)?
Note that the enums are in a NuGet package and cannot be modified, e.g. by adding attributes.
You can use a variation of the DefaultValueContractResolver from this answer to Json.NET: How to make DefaultValueHandling only apply to certain types? to exclude all enum-valued properties with default values:
public class EnumDefaultValueContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.DefaultValueHandling == null)
{
if (property.PropertyType.IsEnum)
{
//For safety you could check here if the default value is named ValueNotSet and only set IgnoreAndPopulate in that case.
//var defaultValue = Enum.ToObject(property.PropertyType, 0);
//if (defaultValue.ToString() == "ValueNotSet")
//{
property.DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate; // Or DefaultValueHandling.Ignore if you prefer
//}
}
}
return property;
}
}
Then use it as follows:
var resolver = new EnumDefaultValueContractResolver();
var settings = new JsonSerializerSettings { ContractResolver = resolver };
var json = JsonConvert.SerializeObject(inventory, settings);
You may want to cache the contract resolver for best performance.
Demo fiddle here.
Many serializers, (including Json.NET I believe) support the ShouldSerialize*() pattern; if you don't mind doing this on a per-usage basis, you could do:
public class DanishInventory {
public int QtyInStock;
public Flavor;
public bool ShouldSerializeFlavor() => Flavor != 0;
}
In order to avoid losing JSON properties when deserializating to a POCO that is missing members, I use the [JsonExtensionData] attribute. Ex:
public class Foo
{
public int Y { get; set; }
[JsonExtensionData]
private IDictionary<string, JToken> _extraStuff;
}
That way, if I try to deserialize the following, I won't lose the z property:
{
"y": 1,
"z": "added in foo"
}
So far so good.
But in reality, I have a really deep object graph. So every POCO in the graph must use the [JsonExtensionData] attribute. This is a little dangerous. As soon as I forget to add this in one of the class, doing a deserialization followed by a serialization will lose data. (the real use case is doing a HTTP GET followed by a HTTP POST and I want to be sure that I don't lose anything)
So, to be sure that I haven't forgotten any [JsonExtensionData] in my whole POCO object tree, I thought about using the following deserializer setting:
var serializerSettings = new JsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Error
};
But then, if I try to deserialize the previous JSON, I get the following exception:
Could not find member 'z' on object of type 'Foo'. Path 'z', line 3, position 6.
This is a quite annoying, it complains about a field that has no member in the POCO but that is covered by the [JsonExtensionData] attribute.
Is there a way to only raise errors when data is actually data being lost during the deserialization?
You may mark your object with [JsonObject(MissingMemberHandling = MissingMemberHandling.Ignore)]. This will override the serializer setting:
[JsonObject(MissingMemberHandling = MissingMemberHandling.Ignore)]
public class Foo
{
public int Y { get; set; }
[JsonExtensionData]
private IDictionary<string, JToken> _extraStuff;
}
Demo fiddle #1 here.
Alternatively, you could create a custom contract resolver that does this automatically:
public class MissingMemberContractResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
if (contract.ExtensionDataSetter != null && contract.MissingMemberHandling == null)
{
contract.MissingMemberHandling = MissingMemberHandling.Ignore;
}
return contract;
}
}
Then use it as follow. First cache a copy somewhere for performance:
static IContractResolver contractResolver = new MissingMemberContractResolver
{
// Modify settings such as the naming strategy if required.
NamingStrategy = new CamelCaseNamingStrategy(),
};
And then set in settings as follows:
var serializerSettings = new JsonSerializerSettings
{
ContractResolver = contractResolver,
MissingMemberHandling = MissingMemberHandling.Error
};
var foo = JsonConvert.DeserializeObject<Foo>(json, serializerSettings);
var json2 = JsonConvert.SerializeObject(foo, Formatting.Indented, serializerSettings);
Demo fiddle #2 here.
Note that MissingMemberHandling was added to to JsonObjectAttribute and JsonObjectContract in Json.NET release 12.0.2. On earlier versions neither of the above solutions are available.
Honestly I'm a bit surprised this is necessary.
As an aside, if you're creating a custom contract resolver anyway, you could make DefaultContractResolver.CreateObjectContract() throw for any object in your assembly or namespace that lacks an ExtensionDataGetter and ExtensionDataSetter. If you do that you'll be able to discover any types that lack a [JsonExtensionData] during unit testing.
I have a third-party class (lets call it Class1) which I need to serialize to JSON. If I try to do this as is, I either receive StackOverflowException or JsonSerializationException with message "Self referencing loop detected with type". I've tried to apply the following settings for the JsonConvert but it didn't help me to avoid StackOverflowException
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.None,
PreserveReferencesHandling = PreserveReferencesHandling.None,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
After decompiling the Class1 I found out that a lot of properties of the Class1 are marked with [ScriptIgnore] attribute which is an analogue of [JsonIgnore] and is used by System.Web.Script.Serialization.JavaScriptSerializer but I need to use Newtonsoft serializer.
As far as Class1 is a third-party class I can't add [JsonIgnore] attribute to the needed properties.
I know that I can develop my own implementation of IContractResolver, and handle the problematic properties there, but I'd like to avoid this option.
Maybe there is a way somehow configure Newtonsoft serializer to take into consideration [ScriptIgnore] attribute as well as [JsonIgnore]. And do this configuration like it is done with ReferenceLoopHandling?
I would appreciate for any ideas.
There's no configuration option for this. If you search on github, ScriptIgnoreAttribute doesn't even appear in the Json.NET source tree.
Even though you don't want to implement your own IContractResolver, this would be the straightforward solution and very easy. First, define the following subclass of DefaultContractResolver as follows:
public class ScriptIgnoreContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (!property.Ignored)
{
if (property.AttributeProvider.GetAttributes(true).Any(p => p.GetType().FullName == "System.Web.Script.Serialization.ScriptIgnoreAttribute"))
{
property.Ignored = true;
}
}
return property;
}
}
Then serialize as follows:
// Define a static member
static readonly IContractResolver myResolver = new ScriptIgnoreContractResolver();
// And use it in your serialization method
var settings = new JsonSerializerSettings
{
ContractResolver = myResolver,
};
var json = JsonConvert.SerializeObject(rootObject, settings);
You may want to cache the contract resolver for best performance.
I'm working on a ASP.NET WebApi (Release Candidate) project where I'm consuming several DTOs that are marked with the [Serializable] attribute. These DTOs are outside of my control so I'm not able to modify them in any way. When I return any of these from a get method the resulting JSON contains a bunch of k__BackingFields like this:
<Name>k__BackingField=Bobby
<DateCreated>k__BackingField=2012-06-19T12:35:18.6762652-05:00
Based on the searching I've done this seems like a problem with JSON.NET's IgnoreSerializableAttribute setting and to resolve my issue I just need to set it globally as the article suggests. How do I change this setting globally in a ASP.NET Web api project?
I found easy way to get rid of k__BackingField in the names.
This fragment should be somewhere in the Application_Start() in Global.asax.cs:
JsonSerializerSettings jSettings = new Newtonsoft.Json.JsonSerializerSettings();
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings = jSettings;
Looks like the default setting takes care of it.
Since the library does not expose a static setter for the DefaultContractResolver, I suggest you create a static wrapper over JsonConvert and it's Serialize*/Deserialize* methods (at least the ones you use).
In your static wrapper you can define a static contract resolver:
private static readonly DefaultContractResolver Resolver = new DefaultContractResolver
{
IgnoreSerializableAttribute = true
};
This you can pass to each serialization method in the JsonSerializerSettings, inside your wrapper.
Then you call your class throughout your project.
The alternative would be to get the JSON.NET source code and adjust it yourself to use that attribute by default.
For me, the following fixed the issue with circular references and k__BackingField.
In your WebApiConfig add the following to the Register() method:
JsonSerializerSettings jSettings = new Newtonsoft.Json.JsonSerializerSettings {
ContractResolver = new DefaultContractResolver {
IgnoreSerializableAttribute = true
},
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings = jSettings;
Friends, don't declare properties like this:
public String DiscretionCode { get; set; }
But, create auxiliar vars, like old....
private String discretionCode;
public String DiscretionCode
{
get { return discretionCode;}
set { discretionCode = value; }
}
The following class does not deserialize (but does serialize) using System.Web.Script.Serialization.JavaScriptSerializer.
public class foo {
public KeyValuePair<string, string>? bar {get;set;}
}
The attempt to deserialize results in a System.NullReferenceException when System.Web.Script.Serialization.ObjectConverter.ConvertDictionaryToObject reaches the bar property. (Note, that is a surmise based on the stack trace.)
Changing the property type to KeyValuePair<string,string> fixes the problem, but I'd like to keep the Nullable type if at all possible.
The JSON is exactly what you would expect:
{"foo": {
"bar": {
"Key":"Jean-Luc",
"Value":"Picard"
}
}}
Help?
The reason this happens is that when the JavaScriptSerializer tries to deserialize it will create a new instance of the class (in this the KeyValuePair) and then assign the values to properties.
This causes an issue as the KeyValuePair can only have the key and values assigned as part of the constructor and not via properties so results in an empty key and value.
You will be able to resolve this and the null issue by creating a class that implements JavaScriptConverter and Registering It. I have used the code below to handle a standard KeyValuePair but I am sure you can extend it to handle nulls.
public class DictionaryJavaScriptConverter<k, v> : JavaScriptConverter
{
public override object Deserialize(System.Collections.Generic.IDictionary<string, object> dictionary, System.Type type, System.Web.Script.Serialization.JavaScriptSerializer serializer)
{
return new KeyValuePair<k, v>((k)dictionary["Key"], (v)dictionary["Value"]);
}
public override System.Collections.Generic.IDictionary<string, object> Serialize(object obj, System.Web.Script.Serialization.JavaScriptSerializer serializer)
{
throw new NotImplementedException();
}
public override System.Collections.Generic.IEnumerable<System.Type> SupportedTypes {
get { return new System.Type[] { typeof(KeyValuePair<k, v>) }; }
}
}
Alternately you can create a simple class that has two properties key and value.
You can have a look at this wrapper:
http://www.codeproject.com/KB/aspnet/Univar.aspx
I've successfully Json serialized and deserialized the nullable KeyValue pair using it.