I want to serialize specific private fields that belongs to unmodifiable library class, how can i do that?
To serialize private fields, I know that I can modify settings to serialize all private fields, but I do not want to serialize all fields. Just the specific fields.
Also to serialize private fields, I know that JsonProperty or DataMember attribiutes can be written on the private fields. But I can not do that, because I can not modify library classes. Even if I can modify it, the library depends on an external library after modifying the library class (For example it depends on Newtonsoft.Json.dll if JsonProperty attribute is used).
So I tried to create a custom attribute to solve it.
// The class to serialize
public class FeatureRepository
{
// Serialize all private properties that belongs to this object.
public IDGenerator FeatureIDs;
// Do not serialize any private property that belongs to this object!
public IDGenerator oldFeatureIDs;
}
// Unmodifiable library class. Also it must be independent from any external libraries.
// There are some private fields but they do not seem.
public class IDGenerator
{
public IDGenerator();
public uint GenerateID();
public bool IsIDUsed(uint id);
public void ReleaseID(uint id);
public void UseID(uint id);
}
// Custom contract resolver
public class MyContractResolver : DefaultContractResolver
{
// Target properties
private List<SerializableJson> serializableJsons = new List<SerializableJson>();
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
JsonObjectContract contract = base.CreateObjectContract(objectType);
// Detect SerializableJsonAttribute and store the property info
foreach (JsonProperty property in contract.Properties)
{
IList<Attribute> attributes = property.AttributeProvider.GetAttributes(typeof(SerializableJsonAttribute), false);
foreach (SerializableJsonAttribute temp in attributes)
{
if (temp != null)
{
SerializableJson serializableJson = new SerializableJson()
{
PropertyType = property.PropertyType,
PropertyName = property.PropertyName
};
serializableJsons.Add(serializableJson);
}
}
}
return contract;
}
protected override List<MemberInfo> GetSerializableMembers(Type objectType)
{
List<MemberInfo> members = base.GetSerializableMembers(objectType);
// It can compare just the type, can not compare the property name here.
SerializableJson serializableJson = serializableJsons.Where(temp => temp.PropertyType == objectType).FirstOrDefault();
// If the type is target type, add also private fields.
if (serializableJson != null)
{
IEnumerable<MemberInfo> nonPublicMembers = GetFieldsAndProperties(objectType, BindingFlags.NonPublic)
.Where(m => m is PropertyInfo p ? !IsIndexedProperty(p) : true);
foreach (MemberInfo member in nonPublicMembers)
members.Add(member);
}
return members;
}
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Class, AllowMultiple = false)]
public class SerializableJsonAttribute : Attribute
{
}
public class SerializableJson
{
public Type PropertyType { get; set; }
public string PropertyName { get; set; }
}
// Finally use the custom attribute SerializableJson
// Both of them will serialized with theirs private fields.
public class FeatureRepository
{
[SerializableJson]
public IDGenerator FeatureIDs; // I want to serialize all private properties that belongs to this object.
public IDGenerator oldFeatureIDs; // I do not want to serialize any private property that belongs to this object!
}
How can I solve it?
Or is there any different way to solve this problem than the way I have tried?
Blockquote
Edit Let me explain the problem step by step: Firstly, FeatureRepository is serialized, SerializableJsonAttribute is searched for all properties. It finds the attribute on the FeatureIDs property but oldFeatureIDs. So it stores property type "IDGenerator" and property name "FeatureIDs". Secondly it starts to serialize IDGenerator. While serializing IDGenerator, you can not know that the serialization is done for "FeatureIDs" property or "oldFeatureIDs" property. If you would know, you can add private fields for "FeatureIDs" property.
https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm shows you can filter by property name.
ShouldSerialize can also be set using an IContractResolver. Conditionally serializing a property using an IContractResolver is useful if you don't want to place a ShouldSerialize method on a class or you didn't declare the class and are unable to.
The page contains full implementation:
(...)
if (property.DeclaringType == typeof(Employee) && property.PropertyName == "Manager")
{
property.ShouldSerialize = ...
(...)
Related
The JSON response from my ASP.NET Core 3.1 API controller is missing properties. This happens when a property uses a derived type; any properties defined in the derived type but not in the base/interface will not be serialized to JSON. It seems there is some lack of support for polymorphism in the response, as if serialization is based on a property's defined type instead of its runtime type. How can I change this behavior to ensure that all public properties are included in the JSON response?
Example:
My .NET Core Web API Controller returns this object that has a property with an interface type.
// controller returns this object
public class Result
{
public IResultProperty ResultProperty { get; set; } // property uses an interface type
}
public interface IResultProperty
{ }
Here is a derived type that defines a new public property named Value.
public class StringResultProperty : IResultProperty
{
public string Value { get; set; }
}
If I return the derived type from my controller like this:
return new MainResult {
ResultProperty = new StringResultProperty { Value = "Hi there!" }
};
then the actual response includes an empty object (the Value property is missing):
I want the response to be:
{
"ResultProperty": { "Value": "Hi there!" }
}
While the other answers are good and solves the problem, if all you want is the general behavior to be like pre netcore3, you can use the Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet package and in Startup.cs do:
services.AddControllers().AddNewtonsoftJson()
More info here. This way, you don't need to create any extra json-converters.
I ended up creating a custom JsonConverter (System.Text.Json.Serialization namespace) which forces JsonSerializer to serialize to the object's runtime type. See the Solution section below. It's lengthy but it works well and does not require me to sacrifice object oriented principles in my API's design. (If you need something quicker and can use Newtonsoft then check out the top voted answer instead.)
Some background: Microsoft has a System.Text.Json serialization guide with a section titled Serialize properties of derived classes with good information relevant to my question. In particular it explains why properties of derived types are not serialized:
This behavior is intended to help prevent accidental exposure of data
in a derived runtime-created type.
If that is not a concern for you then the behavior can be overridden in the call to JsonSerializer.Serialize by either explicitly specifying the derived type or by specifying object, for example:
// by specifying the derived type
jsonString = JsonSerializer.Serialize(objToSerialize, objToSerialize.GetType(), serializeOptions);
// or specifying 'object' works too
jsonString = JsonSerializer.Serialize<object>(objToSerialize, serializeOptions);
To accomplish this with ASP.NET Core you need to hook into the serialization process. I did this with a custom JsonConverter that calls JsonSerializer.Serialize one of the ways shown above. I also implemented support for deserialization which, while not explicitly asked for in the original question, is almost always needed anyway. (Oddly, supporting only serialization and not deserialization proved to be tricky anyway.)
Solution
I created a base class, DerivedTypeJsonConverter, which contains all of the serialization & deserialization logic. For each of your base types, you would create a corresponding converter class for it that derives from DerivedTypeJsonConverter. This is explained in the numbered directions below.
This solution follows the "type name handling" convention from Json.NET which introduces support for polymorphism to JSON. It works by including an additional $type property in the derived type's JSON (ex: "$type":"StringResultProperty") that tells the converter what the object's true type is. (One difference: in Json.NET, $type's value is a fully qualified type + assembly name, whereas my $type is a custom string which helps future-proof against namespace/assembly/class name changes.) API callers are expected to include $type properties in their JSON requests for derived types. The serialization logic solves my original problem by ensuring that all of the object's public properties are serialized, and for consistency the $type property is also serialized.
Directions:
1) Copy the DerivedTypeJsonConverter class below into your project.
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
public abstract class DerivedTypeJsonConverter<TBase> : JsonConverter<TBase>
{
protected abstract string TypeToName(Type type);
protected abstract Type NameToType(string typeName);
private const string TypePropertyName = "$type";
public override bool CanConvert(Type objectType)
{
return typeof(TBase) == objectType;
}
public override TBase Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// get the $type value by parsing the JSON string into a JsonDocument
JsonDocument jsonDocument = JsonDocument.ParseValue(ref reader);
jsonDocument.RootElement.TryGetProperty(TypePropertyName, out JsonElement typeNameElement);
string typeName = (typeNameElement.ValueKind == JsonValueKind.String) ? typeNameElement.GetString() : null;
if (string.IsNullOrWhiteSpace(typeName)) throw new InvalidOperationException($"Missing or invalid value for {TypePropertyName} (base type {typeof(TBase).FullName}).");
// get the JSON text that was read by the JsonDocument
string json;
using (var stream = new MemoryStream())
using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Encoder = options.Encoder })) {
jsonDocument.WriteTo(writer);
writer.Flush();
json = Encoding.UTF8.GetString(stream.ToArray());
}
// deserialize the JSON to the type specified by $type
try {
return (TBase)JsonSerializer.Deserialize(json, NameToType(typeName), options);
}
catch (Exception ex) {
throw new InvalidOperationException("Invalid JSON in request.", ex);
}
}
public override void Write(Utf8JsonWriter writer, TBase value, JsonSerializerOptions options)
{
// create an ExpandoObject from the value to serialize so we can dynamically add a $type property to it
ExpandoObject expando = ToExpandoObject(value);
expando.TryAdd(TypePropertyName, TypeToName(value.GetType()));
// serialize the expando
JsonSerializer.Serialize(writer, expando, options);
}
private static ExpandoObject ToExpandoObject(object obj)
{
var expando = new ExpandoObject();
if (obj != null) {
// copy all public properties
foreach (PropertyInfo property in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead)) {
expando.TryAdd(property.Name, property.GetValue(obj));
}
}
return expando;
}
}
2) For each of your base types, create a class that derives from DerivedTypeJsonConverter. Implement the 2 abstract methods which are for mapping $type strings to actual types. Here is an example for my IResultProperty interface that you can follow.
public class ResultPropertyJsonConverter : DerivedTypeJsonConverter<IResultProperty>
{
protected override Type NameToType(string typeName)
{
return typeName switch
{
// map string values to types
nameof(StringResultProperty) => typeof(StringResultProperty)
// TODO: Create a case for each derived type
};
}
protected override string TypeToName(Type type)
{
// map types to string values
if (type == typeof(StringResultProperty)) return nameof(StringResultProperty);
// TODO: Create a condition for each derived type
}
}
3) Register the converters in Startup.cs.
services.AddControllers()
.AddJsonOptions(options => {
options.JsonSerializerOptions.Converters.Add(new ResultPropertyJsonConverter());
// TODO: Add each converter
});
4) In requests to the API, objects of derived types will need to include a $type property. Example JSON: { "Value":"Hi!", "$type":"StringResultProperty" }
Full gist here
The documentation shows how to serialize as the derived class when calling the serializer directly. The same technique can also be used in a custom converter that we then can tag our classes with.
First, create a custom converter
public class AsRuntimeTypeConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return JsonSerializer.Deserialize<T>(ref reader, options);
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, value?.GetType() ?? typeof(object), options);
}
}
Then mark the relevant classes to be used with the new converter
[JsonConverter(typeof(AsRuntimeTypeConverter<MyBaseClass>))]
public class MyBaseClass
{
...
Alternately, the converter can be registered in startup.cs instead
services
.AddControllers(options =>
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new AsRuntimeTypeConverter<MyBaseClass>());
}));
I had a similar issue, where I was returning an enumerable of type TAnimal (but the object instances were of derived types such as Dog, Cat, etc.):
[HttpGet]
public IEnumerable<TAnimal> GetAnimals()
{
IEnumerable<TAnimal> list = GetListOfAnimals();
return list;
}
This only included properties defined in TAnimal.
However, in ASP .NET Core 3.1 at least, I found that I could just cast the object instances to object, and the JSON serializer then included all the properties from the derived classes:
[HttpGet]
public IEnumerable<object> GetAnimals()
{
IEnumerable<TAnimal> list = GetListOfAnimals();
return list.Select(a => (object)a);
}
(Note that the signature of the GetAnimals method must also changed, but that doesn't usually matter much in a web API context). If you need to provide type information for Swagger or whatever, you can annotate the method:
[HttpGet]
[Produces(MediaTypeNames.Application.Json, Type = typeof(TAnimal[]))]
public IEnumerable<object> GetAnimals()
{
...
}
Casting to object is a simple solution if you only have a 1-layer-deep object hierarchy to worry about.
This is the expected result. You're upcasting when you do that, so what will be serialized is the upcasted object, not the actual derived type. If you need stuff from the derived type, then that has to be the type of the property. You may want to use generics for this reason. In other words:
public class Result<TResultProperty>
where TResultProperty : IResultProperty
{
public TResultProperty ResultProperty { get; set; } // property uses an interface type
}
Then:
return new Result<StringResultProperty> {
ResultProperty = new StringResultProperty { Value = "Hi there!" }
};
I solved it by writing this extension:
public static class JsonSerializationExtensions
{
public static string ToJson<T>(this IEnumerable<T> enumerable, bool includeDerivedTypesProperties = true)
where T : class
{
var jsonOptions = new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
if (includeDerivedTypeProperties)
{
var collection = enumerable.Select(e => e as object).ToList();
return JsonSerializer.Serialize<object>(collection, jsonOptions);
}
else
{
return JsonSerializer.Serialize(enumerable, jsonOptions);
}
}
}
I was also struggling with this in a .NET Core 3.1 API, where I wanted the result to include $type attribute.
As suggested, install the correct package and then 'AddNewtonsoftJson'.
I wanted the $type field to be added to show the derived type handling, to get that
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All;
});
Not knocking Newtonsoft, but I found an easier way to resolve this with the built handlers.
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "/emps", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
List<emp> GetEmps();
//[DataContract(Namespace = "foo")] <<< comment/removed this line
public class emp
{
public string userId { get; set; }
public string firstName { get; set; }
}
public class dept
{
public string deptId{ get; set; }
public string deptName{ get; set; }
}
In my case dept objects where working fine, but emp ones were not - they came across as empty.
I want to use CsvHelper.Configuration.ClassMap by dynamically assigned properties.
Usually you map a Property like this in a static manner: You have to assign each property and its 'text to display'.
using CsvHelper.Configuration;
public sealed class CleanSQLRowDescriptorMap : ClassMap<CleanSQLRowDescriptor>
{
public CleanSQLRowDescriptorMap()
{
Map(f => f.OriginalIndex).Name("Original Index");
Map(f => f.OriginalRow).Name("Original Row");
}
}
I want to do the following:
using CsvHelper.Configuration;
public sealed class CleanSQLRowDescriptorMap : ClassMap<CleanSQLRowDescriptor>
{
public CleanSQLRowDescriptorMap()
{
// Filter by attribute (implementation returns PropertyInfo List)
List<PropertyInfo> mappedProperties = CleanSQLRowDescriptor.Create().FilterPropertiesByAttribute();
// Dynamically assign each property and its assigned 'attribute value'
// At the moment I mapped the PropertyInfo.Name, but I actually need to use the Property as the static example above.
// Also need to figure out how to get the Attribute value (DisplayName in this example).
mappedProperties.ForEach(prop => Map(f => prop.Name).Name(prop.Name));
}
}
I currently have the following method used above:
[DisplayName("Original Index")]
public int OriginalIndex { get; set; }
[DisplayName("Original Row")]
public string OriginalRow { get; set; }
public string DonotWantToAssignThis { get; set; }
public List<PropertyInfo> FilterPropertiesByAttribute()
{
// This function already returns only the attributes that use
// [DisplayName] and other attributes defined for other properties,
// ignoring other properties that do not have any of these attributes.
return properties;
}
How can I use the PropertyInfo List of items to dynamically assign the ClassMap? I want to create a base class with these attributes as filters and all the classes implementing this base class would have the same capability, making it easier to 'maintain the mappings'.
I managed to figure it out, VS Code did not give me all the overloads for Map() function, so I missed overloads.
This one is used in all examples:
MemberMap<TClass, TMember> Map<TMember>(Expression<Func<TClass, TMember>> expression, bool useExistingMap = true);
I found this inside JoshClose/CSVHelper:
public MemberMap Map(Type classType, MemberInfo member, bool useExistingMap = true)
So instead of using 'Expression that requires the property name as TMember' which does not take the type I can now assign the MemberInfo directly.
The code below just shows a solution for a single attribute [DisplayName] by using its .DisplayName property value.
For additional Attributes like I have at the moment, I will need to handle the property value differently:
mappedProperties.ForEach(prop =>
{
Map(typeof(CleanSQLRowDescriptor), prop).Name(prop.GetCustomAttribute<DisplayNameAttribute>().DisplayName);
});
My problem is quite simple..
What I would like to do is this:
[JsonConverter(typeof(MyConverter)]
object SomeProperty {get;set;}
But be able to write it as a custom attribute so I simply can decorate my properties with a pre-defined JsonConverter attribute.. so for instance
[MyCustomConverter]
object SomeProperty {get;set;}
Would in this case be treated as [JsonConverter(typeof(MyConverter))]
Any ideas?
Br,
Inx
It's not trivial, but you can do this if you implement a custom IContractResolver that takes your attributes into account.
There are several steps involved in doing this:
Create an abstract base class for your attributes that extends System.Attribute:
public abstract class ConverterAttribute : Attribute
{
public abstract JsonConverter Converter { get; }
}
Next, you need to create the IContractResolver that will actually use your attribute1:
public class CustomAttributeContractResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
JsonObjectContract contract =
base.CreateObjectContract(objectType);
IEnumerable<JsonProperty> withoutConverter =
contract.Properties.Where(
pr => pr.MemberConverter == null &&
pr.Converter == null);
// Go over the results of calling the default implementation.
// check properties without converters for your custom attribute
// and pull the custom converter out of that attribute.
foreach (JsonProperty property in withoutConverter)
{
PropertyInfo info =
objectType.GetProperty(property.UnderlyingName);
var converterAttribute =
info.GetCustomAttribute<ConverterAttribute>();
if (converterAttribute != null)
{
property.Converter = converterAttribute.Converter;
property.MemberConverter = converterAttribute.Converter;
}
}
return contract;
}
}
Create the attribute that overrrides ConverterAttribute.Converter, returning your custom converter:
public class MyCustomConverterAttribute : ConverterAttribute
{
get { return new MyCustomConverter(); }
}
Decorate your class with the attribute:
public class MyClass
{
[MyCustomConverter]
public object MyProperty { get; set; }
}
When serializing or deserializing, specify the contract resolver in the JsonSerializerSettings you use:
var settings = new JsonSerializerSettings();
settings.ContractResolver = new CustomAttributeContractResolver();
string serialized = JsonConverter.SerializeObject(new MyClass());
I'd say that this probably isn't worth the small benefit--all you're really doing is saving a few characters, unless your attribute does something else.
1: I'm not sure what the difference between MemberConverter and Converter. When serializing, only the Converter property was needed, but deserializing required MemberConverter. I'll keep digging, but hopefully someone can provide some insight. Looks like others have had this same question as well.
It does not appear to be possible. The JsonConverterAttribute and TypeConverterAttribute classes are both sealed, and these are the classes used by Json.NET to supply a custom type converter.
I am using EF5 database first. In order to serialize a generated POCO class, I need to use the XMLIgnore attribute as follows
public partial class Demographics
{
public string KeyTract { get; set; }
public string CollectionYear { get; set; }
public string MSAFIPS { get; set; }
...
...
...
[XmlIgnore()]
public virtual ICollection<LoanApp> LoanApps { get; set; }
}
In order to keep from adding this each time I have EF re-create the model from the database, I added a "buddy class"
[MetadataType(typeof(Demographic_Metadata))]
public partial class Demographics
{
}
public class Demographic_Metadata
{
[XmlIgnore()]
public virtual ICollection<LoanApp> LoanApps { get; set; }
}
But when I attempt to serialize the Demographics object using XmlSerializer I get "There was an error reflecting type ...Demographics".
In researching SO it appears that XMLSerializer ignores the buddy class. But has anyone found a workaround to avoid adding the XMLIgnore attribute each time the POCO class is regenerated?
You can do this by using the XmlSerializer overload where you pass in the XmlAttributeOverrides. All we need to do is populate it via TypeDescriptor and make an ICustomAttributeProvider.
first the ICustomAttributeProvider is the simplest one, mainly because I'm just ignoring the inhert flag and always returning back all the attributes. The idea is that we will just pass in the attributes we want the XmlSerializer to know about.
public class CustomAttributeProvider : ICustomAttributeProvider
{
private readonly object[] _attributes;
public CustomAttributeProvider(params Attribute[] attributes)
{
_attributes = attributes;
}
public CustomAttributeProvider(AttributeCollection attributeCollection)
: this(attributeCollection.OfType<Attribute>().ToArray())
{
}
public object[] GetCustomAttributes(bool inherit)
{
return _attributes;
}
public object[] GetCustomAttributes(Type attributeType, bool inherit)
{
return _attributes.Where(attributeType.IsInstanceOfType).ToArray();
}
public bool IsDefined(Type attributeType, bool inherit)
{
return _attributes.Any(attributeType.IsInstanceOfType);
}
}
Now I'm going to create a factory method to create the XMLAttributeOverrides.
We need to tell TypeDescriptor about the buddy class and that's what adding AssociatedMetadataTypeTypeDescriptionProvider to the provider list does.
From that we use TypeDescriptor to read the class attributes and the properties. Since buddy classes don't allow fields and TypeDescriptor doesn't read the fields either, so I use reflection for fields.
public static class BuddyClass
{
public static XmlAttributeOverrides CreateXmlAttributeOverrides(Type type)
{
// Tell TypeDescriptor about the buddy class
TypeDescriptor.AddProvider(new AssociatedMetadataTypeTypeDescriptionProvider(type), type);
var xmlAttributeOverrides = new XmlAttributeOverrides();
xmlAttributeOverrides.Add(type, new XmlAttributes(new CustomAttributeProvider(TypeDescriptor.GetAttributes(type))));
foreach (PropertyDescriptor props in TypeDescriptor.GetProperties(type))
{
if (props.Attributes.Count > 0)
{
xmlAttributeOverrides.Add(type, props.Name, new XmlAttributes(new CustomAttributeProvider(props.Attributes)));
}
}
foreach (var field in type.GetFields())
{
var attributes = field.GetCustomAttributes(true).OfType<Attribute>().ToArray();
if (attributes.Any())
{
xmlAttributeOverrides.Add(type, field.Name, new XmlAttributes(new CustomAttributeProvider(attributes)));
}
}
return xmlAttributeOverrides;
}
}
You would call it like
var serializer = new XmlSerializer(typeof(Test), BuddyClass.CreateXmlAttributeOverrides(typeof(Test)));
Then user serializer like you normally would. This doesn't handle the attributes on parameters or return values.
Given a data model:
[DataContract]
public class Parent
{
[DataMember]
public IEnumerable<ChildId> Children { get; set; }
}
[DataContract]
public class ChildId
{
[DataMember]
public string Id { get; set; }
}
[DataContract]
public class ChildDetail : ChildId
{
[DataMember]
public string Name { get; set; }
}
For implementation convenience reasons, there are times when the ChildId objects on the Parent are in fact ChildDetail objects. When I use JSON.net to serialize the Parent, they are written out with all of the ChildDetail properties.
Is there any way to instruct JSON.net (or any other JSON serializer, I'm not far enough into the project to be committed to one) to ignore derived class properties when serializing as a base class?
EDIT: It is important that when I serialize the derived class directly that I'm able to produce all the properties. I only want to inhibit the polymorphism in the Parent object.
I use a custom Contract Resolver to limit which of my properties to serialize. This might point you in the right direction.
e.g.
/// <summary>
/// json.net serializes ALL properties of a class by default
/// this class will tell json.net to only serialize properties if they MATCH
/// the list of valid columns passed through the querystring to criteria object
/// </summary>
public class CriteriaContractResolver<T> : DefaultContractResolver
{
List<string> _properties;
public CriteriaContractResolver(List<string> properties)
{
_properties = properties
}
protected override IList<JsonProperty> CreateProperties(
JsonObjectContract contract)
{
IList<JsonProperty> filtered = new List<JsonProperty>();
foreach (JsonProperty p in base.CreateProperties(contract))
if(_properties.Contains(p.PropertyName))
filtered.Add(p);
return filtered;
}
}
In the override IList function, you could use reflection to populate the list with only the parent properties perhaps.
Contract resolver is applied to your json.net serializer. This example is from an asp.net mvc app.
JsonNetResult result = new JsonNetResult();
result.Formatting = Formatting.Indented;
result.SerializerSettings.ContractResolver =
new CriteriaContractResolver<T>(Criteria);
I had the exact same problem and looked up how to build the ContractResolver I was actually looking for and that better answer this question. This only serializes the Properties of the Type T you actually want to serialize, but with this example you can also easily build similar approaches:
public class TypeOnlyContractResolver<T> : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = instance => property.DeclaringType == typeof (T);
return property;
}
}
Having encountered a similar problem, this is the ContractResolver I came up with:
public class StrictTypeContractResolver : DefaultContractResolver
{
private readonly Type _targetType;
public StrictTypeContractResolver( Type targetType ) => _targetType = targetType;
protected override IList<JsonProperty> CreateProperties( Type type, MemberSerialization memberSerialization )
=> base.CreateProperties
(
_targetType.IsAssignableFrom( type ) ? _targetType : type,
memberSerialization
);
}
It cuts off only the properties of targetType's descendants, without affecting the properties of its base classes or of other types that targetType's properties might reference. Which, depending on your needs, may or may not be an improvement over the other answers provided here at the time.
Check out the answers in this similar thread, particularly the IgnorableSerializerContractResolver in my answer and the nicer lambda version
Usage:
var jsonResolver = new IgnorableSerializerContractResolver();
// ignore single property
jsonResolver.Ignore(typeof(Company), "WebSites");
// ignore single datatype
jsonResolver.Ignore(typeof(System.Data.Objects.DataClasses.EntityObject));
var jsonSettings = new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, ContractResolver = jsonResolver };
I have not used JSON.Net in particular so not positive this will help you. If JSON.Net derives itself from the .Net serialization system then you should be able to add the [NonSerialized] attribute to your properties you do now wish to be serialized in the base class. When you call the serialize methods on the base class, serialization should then skip those elements.
Haven't compared performance implications, but this is a working solution as well, and works with nested/referenced objects as well.
Derived d = new Derived();
string jsonStringD = JsonConvert.SerializeObject(d);
Base b = new Base();
JsonConvert.PopulateObject(jsonStringD, b);
string jsonStringB = JsonConvert.SerializeObject(b);