I am looking at building an API using WebAPI in ASP.Net.
I have a requirement to conditionally exclude properties from the XML or JSON based on some custom logic at RunTime and not Compile Time.
I have to remove the xml or json from the response, it is no good just including the tags with a null or empty value.
I have tried various approaches, none of which I seem to be able to get to work.
I have tried the following
Delegating Handler from here
public class ResponseDataFilterHandler : DelegatingHandler
{
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
var response = task.Result;
//Manipulate content here
var content = response.Content as ObjectContent;
if (content != null && content.Value != null)
{
}
//Or replace the content
//response.Content = new ObjectContent(typeof(object), new object(), new MyFormatter());
return response;
});
}
}
Sure I can null properties out here, but they still appear in the response.
DataContractSurrogate similar to this
public class MySurrogate: IDataContractSurrogate
{
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
return null;
}
public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
{
return null;
}
public Type GetDataContractType(Type type)
{
return null;
}
public object GetDeserializedObject(object obj, Type targetType)
{
return null;
}
public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
{
}
public object GetObjectToSerialize(object obj, Type targetType)
{
if (obj == null) return null;
var type = obj.GetType();
type.GetProperties().ToList()
.ForEach(prop =>
{
try
{
var attr = prop.GetCustomAttributes(typeof(ConditionalDataMemberAttribute), false);
if (attr.Any())
{
var proptype = prop.PropertyType;
//Set the property value to its default value
prop.GetSetMethod().Invoke(obj,
new[] { proptype.IsValueType ? Activator.CreateInstance(proptype) : null });
}
}
catch { }
});
return obj;
}
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
return null;
}
public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
{
return null;
}
}
Again, I can just null the properties out, I cannot remove the xml or json from the output.
I have an idea where I can dynamically compile a specific class with the required attributes then use the DataContractSurrogate to swap out the original instance with an instance of my new dynamic compiled class - but I don't like it.
Ive tried looking at DataContractSerializer but it is sealed so I cant derive from it - i've also looked to decompile it and make some changes but again it uses internal classes such as DataContract - I feel I need to hook into the serialization but I don't know how to ?
You should use Json.NET and write your own converter
public class MyJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartArray();
// write your object here based on your custom logic
writer.WriteRawValue(value);
writer.WriteEndArray();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
You can use your custom converter like this
string json = JsonConvert.SerializeObject(SomeObject, new MyJsonConverter());
Then, in order to avoid writing a custom Converter for both Json and XML, you can convert your Json to XML
XmlDocument doc = JsonConvert.DeserializeXmlNode(json);
Okay I managed to do this using a bit of what I had already done, plus some of the suggestions on here, I also stumbled upon this
First we start by adding a DelegatingHandler into the pipeline.
config.MessageHandlers.Add(new ResponseDataFilterHandler());
And the class itself
public class ResponseDataFilterHandler : DelegatingHandler
{
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
var response = task.Result;
var content = response.Content as ObjectContent;
if (content != null && content.Value != null)
{
var isJson = response.RequestMessage.GetQueryNameValuePairs().Any(r => r.Key == "json" && r.Value == "true");
response.Content = new StringContent(Helper.GetResponseData(content.Value, isJson));
}
return response;
});
}
}
Then we have a helper class method to get the new serialized string (this is not prod code ;p)
public static class Helper
{
public static string GetResponseData(object root,bool isJson)
{
string json = JsonConvert.SerializeObject(root, new JsonSerializerSettings { ContractResolver = new ShouldSerializeContractResolver()});
if (!isJson)
{
XmlDocument doc = JsonConvert.DeserializeXmlNode(json,"response");
json = doc.OuterXml;
}
return json;
}
}
And finally the ContractReoslver
public class ShouldSerializeContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = (i) =>
{
//Your logic goes here
var r = !property.PropertyName.StartsWith("block-ref");
return r;
};
return property;
}
}
this roots everything through Json and the converts to xml if required, for my test project I am using a querystring (json=true) to specify if the format should be json instead of xml.
Just specify EmitDefaultValue = false like this
[DataContract]
public class MyClass
{
[DataMember]
public int Id { get; set; }
[DataMember(EmitDefaultValue = false)]
public string Name { get; set; }
}
With that, when Name is null, it will not shown up in XML/JSON.
If you want to dynamically null out specific properties, you can provide a method like this.
[OnSerializing]
void OnSerializing(StreamingContext context)
{
if(someConditionIsMet)
this.Name = null;
}
Related
I have a class that has a default constructor and also an overloaded constructor that takes in a set of parameters. These parameters match to fields on the object and are assigned on construction. At this point i need the default constructor for other purposes so i would like to keep it if i can.
My Problem: If I remove the default constructor and pass in the JSON string, the object deserializes correctly and passes in the constructor parameters without any issues. I end up getting back the object populated the way I would expect. However, as soon as I add the default constructor into the object, when i call JsonConvert.DeserializeObject<Result>(jsontext) the properties are no longer populated.
At this point I have tried adding new JsonSerializerSettings(){CheckAdditionalContent = true} to the deserialization call. That did not do anything.
Another note: the constructor parameters do match the names of the fields exactly except that the parameters are start with a lowercase letter. I wouldn't think this would matter since, like i mentioned, the deserialization works fine with no default constructor.
Here is a sample of my constructors:
public Result() { }
public Result(int? code, string format, Dictionary<string, string> details = null)
{
Code = code ?? ERROR_CODE;
Format = format;
if (details == null)
Details = new Dictionary<string, string>();
else
Details = details;
}
Json.Net prefers to use the default (parameterless) constructor on an object if there is one. If there are multiple constructors and you want Json.Net to use a non-default one, then you can add the [JsonConstructor] attribute to the constructor that you want Json.Net to call.
[JsonConstructor]
public Result(int? code, string format, Dictionary<string, string> details = null)
{
...
}
It is important that the constructor parameter names match the corresponding property names of the JSON object (ignoring case) for this to work correctly. You do not necessarily have to have a constructor parameter for every property of the object, however. For those JSON object properties that are not covered by the constructor parameters, Json.Net will try to use the public property accessors (or properties/fields marked with [JsonProperty]) to populate the object after constructing it.
If you do not want to add attributes to your class or don't otherwise control the source code for the class you are trying to deserialize, then another alternative is to create a custom JsonConverter to instantiate and populate your object. For example:
class ResultConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Result));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Load the JSON for the Result into a JObject
JObject jo = JObject.Load(reader);
// Read the properties which will be used as constructor parameters
int? code = (int?)jo["Code"];
string format = (string)jo["Format"];
// Construct the Result object using the non-default constructor
Result result = new Result(code, format);
// (If anything else needs to be populated on the result object, do that here)
// Return the result
return result;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, add the converter to your serializer settings, and use the settings when you deserialize:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new ResultConverter());
Result result = JsonConvert.DeserializeObject<Result>(jsontext, settings);
A bit late and not exactly suited here, but I'm gonna add my solution here, because my question had been closed as a duplicate of this one, and because this solution is completely different.
I needed a general way to instruct Json.NET to prefer the most specific constructor for a user defined struct type, so I can omit the JsonConstructor attributes which would add a dependency to the project where each such struct is defined.
I've reverse engineered a bit and implemented a custom contract resolver where I've overridden the CreateObjectContract method to add my custom creation logic.
public class CustomContractResolver : DefaultContractResolver {
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var c = base.CreateObjectContract(objectType);
if (!IsCustomStruct(objectType)) return c;
IList<ConstructorInfo> list = objectType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).OrderBy(e => e.GetParameters().Length).ToList();
var mostSpecific = list.LastOrDefault();
if (mostSpecific != null)
{
c.OverrideCreator = CreateParameterizedConstructor(mostSpecific);
c.CreatorParameters.AddRange(CreateConstructorParameters(mostSpecific, c.Properties));
}
return c;
}
protected virtual bool IsCustomStruct(Type objectType)
{
return objectType.IsValueType && !objectType.IsPrimitive && !objectType.IsEnum && !objectType.Namespace.IsNullOrEmpty() && !objectType.Namespace.StartsWith("System.");
}
private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
{
method.ThrowIfNull("method");
var c = method as ConstructorInfo;
if (c != null)
return a => c.Invoke(a);
return a => method.Invoke(null, a);
}
}
I'm using it like this.
public struct Test {
public readonly int A;
public readonly string B;
public Test(int a, string b) {
A = a;
B = b;
}
}
var json = JsonConvert.SerializeObject(new Test(1, "Test"), new JsonSerializerSettings {
ContractResolver = new CustomContractResolver()
});
var t = JsonConvert.DeserializeObject<Test>(json);
t.A.ShouldEqual(1);
t.B.ShouldEqual("Test");
Based on some of the answers here, I have written a CustomConstructorResolver for use in a current project, and I thought it might help somebody else.
It supports the following resolution mechanisms, all configurable:
Select a single private constructor so you can define one private constructor without having to mark it with an attribute.
Select the most specific private constructor so you can have multiple overloads, still without having to use attributes.
Select the constructor marked with an attribute of a specific name - like the default resolver, but without a dependency on the Json.Net package because you need to reference Newtonsoft.Json.JsonConstructorAttribute.
public class CustomConstructorResolver : DefaultContractResolver
{
public string ConstructorAttributeName { get; set; } = "JsonConstructorAttribute";
public bool IgnoreAttributeConstructor { get; set; } = false;
public bool IgnoreSinglePrivateConstructor { get; set; } = false;
public bool IgnoreMostSpecificConstructor { get; set; } = false;
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
// Use default contract for non-object types.
if (objectType.IsPrimitive || objectType.IsEnum) return contract;
// Look for constructor with attribute first, then single private, then most specific.
var overrideConstructor =
(this.IgnoreAttributeConstructor ? null : GetAttributeConstructor(objectType))
?? (this.IgnoreSinglePrivateConstructor ? null : GetSinglePrivateConstructor(objectType))
?? (this.IgnoreMostSpecificConstructor ? null : GetMostSpecificConstructor(objectType));
// Set override constructor if found, otherwise use default contract.
if (overrideConstructor != null)
{
SetOverrideCreator(contract, overrideConstructor);
}
return contract;
}
private void SetOverrideCreator(JsonObjectContract contract, ConstructorInfo attributeConstructor)
{
contract.OverrideCreator = CreateParameterizedConstructor(attributeConstructor);
contract.CreatorParameters.Clear();
foreach (var constructorParameter in base.CreateConstructorParameters(attributeConstructor, contract.Properties))
{
contract.CreatorParameters.Add(constructorParameter);
}
}
private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
{
var c = method as ConstructorInfo;
if (c != null)
return a => c.Invoke(a);
return a => method.Invoke(null, a);
}
protected virtual ConstructorInfo GetAttributeConstructor(Type objectType)
{
var constructors = objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(c => c.GetCustomAttributes().Any(a => a.GetType().Name == this.ConstructorAttributeName)).ToList();
if (constructors.Count == 1) return constructors[0];
if (constructors.Count > 1)
throw new JsonException($"Multiple constructors with a {this.ConstructorAttributeName}.");
return null;
}
protected virtual ConstructorInfo GetSinglePrivateConstructor(Type objectType)
{
var constructors = objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
return constructors.Length == 1 ? constructors[0] : null;
}
protected virtual ConstructorInfo GetMostSpecificConstructor(Type objectType)
{
var constructors = objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.OrderBy(e => e.GetParameters().Length);
var mostSpecific = constructors.LastOrDefault();
return mostSpecific;
}
}
Here is the complete version with XML documentation as a gist:
https://gist.github.com/bjorn-jarisch/80f77f4b6bdce3b434b0f7a1d06baa95
Feedback appreciated.
The default behaviour of Newtonsoft.Json is going to find the public constructors. If your default constructor is only used in containing class or the same assembly, you can reduce the access level to protected or internal so that Newtonsoft.Json will pick your desired public constructor.
Admittedly, this solution is rather very limited to specific cases.
internal Result() { }
public Result(int? code, string format, Dictionary<string, string> details = null)
{
Code = code ?? ERROR_CODE;
Format = format;
if (details == null)
Details = new Dictionary<string, string>();
else
Details = details;
}
Based on the answer by Zoltan, I created a variation that lets you use a specific constructor based on its signature.
Usage
return new JsonSerializerSettings
{
ContractResolver = new DynamicObjectResolver(t =>
{
if (t == typeof(QueueProperties))
return new Type[] { typeof(string) };
return null;
})
};
An here is the implementation
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Concurrent;
using System.Reflection;
namespace ConsoleApp76.Json
{
class DynamicObjectResolver : DefaultContractResolver
{
private readonly Func<Type, Type[]> GetConstructorSignature;
private readonly ConcurrentDictionary<Type, ConstructorInfo> TypeToConstructorLookup =
new ConcurrentDictionary<Type, ConstructorInfo>();
public DynamicObjectResolver(Func<Type, Type[]> getConstructorSignature)
{
if (getConstructorSignature is null)
throw new ArgumentNullException(nameof(getConstructorSignature));
GetConstructorSignature = getConstructorSignature;
}
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var result = base.CreateObjectContract(objectType);
ConstructorInfo constructor = TypeToConstructorLookup.GetOrAdd(objectType, t => FindConstructorInfo(t));
if (constructor is null)
return result;
result.OverrideCreator = CreateParameterizedConstructor(constructor);
foreach (var param in CreateConstructorParameters(constructor, result.Properties))
result.CreatorParameters.Add(param);
return result;
}
private ConstructorInfo FindConstructorInfo(Type objectType)
{
Type[] constructorSignature = GetConstructorSignature(objectType);
if (constructorSignature is null)
return null;
return objectType.GetConstructor(
bindingAttr:
System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.NonPublic
| System.Reflection.BindingFlags.Instance,
binder: null,
types: new Type[] { typeof(string) },
modifiers: null);
}
private static ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
{
if (method is null)
throw new ArgumentNullException(nameof(method));
var c = method as ConstructorInfo;
if (c != null)
return a => c.Invoke(a);
return a => method.Invoke(null, a);
}
}
}
Solution:
public Response Get(string jsonData) {
var json = JsonConvert.DeserializeObject<modelname>(jsonData);
var data = StoredProcedure.procedureName(json.Parameter, json.Parameter, json.Parameter, json.Parameter);
return data;
}
Model:
public class modelname {
public long parameter{ get; set; }
public int parameter{ get; set; }
public int parameter{ get; set; }
public string parameter{ get; set; }
}
Working with .net core 2.1 C# 7.1.
I have an API REST server.
I have the following class:
public class ActionTimingAttribute : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// do something before the action executes
DateTime execStart = DateTime.UtcNow;
var request = context.HttpContext.Request;
string payload = ""; // HERE I NEED THE PAYLOAD WITHOUT SENSITIVE DATA
//doing funky stuff
await next();
DateTime execEnd = DateTime.UtcNow;
double elapsed = (execEnd - execStart).TotalMilliseconds;
Log(payload, elapsed);
}
}
Now, I can take the payload with new StreamReader(request.Body) and the rest..
BUT, if I have a password field for example, I dont want the payload to have this value. I want to change the value to '#####' for example..
I have this for a lot of requests and different requests.
For each POST request, I have a class representing the received json...
Obviously I dont care about HttpGet.
A method in my server looks likes this (an example just for login..)
[HttpPost, Route("Auth/Login")]
public async Task<Responses.Login> Login (Requests.Login request)
I assume I need to have some sort of attribute so it would look like:
public class Login
{
public string Email {set;get;}
[SensitiveData]
public string Password {set;get;}
}
But here, I cannot make the final combination where I have the payload and I want to omit any sensitiveData.
If I had the request model, I could look on its fields and check if they are sensitive, and if so to change value in the model (assuming its wont hurt the model the controller actually receives.)
Thanks.
Here is a solution using custom NewtonSoft serialization. The reflection code might have some room for improvement.
public class SensitiveDataAttribute: Attribute
{
}
public sealed class SensitiveDataJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
foreach (PropertyInfo prop in value.GetType().GetProperties())
{
object[] attrs = prop.GetCustomAttributes(true);
if (attrs.Any(x => x is SensitiveDataAttribute))
prop.SetValue(value, "#####");
}
var t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
JObject o = (JObject)t;
o.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead => false;
public override bool CanConvert(Type objectType)
{
return true;
}
}
This is how it is used.
public class MyObject
{
[SensitiveData]
public string Password { get; set; }
public string UserName { get; set; }
}
Inside ActionTimingAttribute : IAsyncActionFilter
private static readonly SensitiveDataJsonConverter SensitiveDataConverter = new SensitiveDataJsonConverter();
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
string safePayload;
if (context.ActionArguments.Count == 1)
{
var safeObject = context.ActionArguments[context.ActionArguments.Keys.ElementAt(0)];
safePayload = JsonConvert.SerializeObject(safeObject, Formatting.None, SensitiveDataConverter);
}
else
safePayload = request.StoreAndGetPayload();
// the rest..
}
To handle not changing the original you can clone like described here: Deep cloning objects
public static class ObjectCopier
{
public static T CloneJson<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
// initialize inner objects individually
// for example in default constructor some list property initialized with some values,
// but in 'source' these items are cleaned -
// without ObjectCreationHandling.Replace default constructor values will be added to result
var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
}
Your usage would then look like this:
safePayload = JsonConvert.SerializeObject(safeObject.CloneJson(), Formatting.None, SensitiveDataConverter);
public class JsonModel
{
[TypeConverter(typeof(CidNumberConvertor))]
[JsonProperty("cid_number")]
public Cid CidNumber;
[TypeConverter(typeof(CidHexaConvertor))]
[JsonProperty("cid_hexa")]
public Cid CidHexa;
[JsonProperty("cid_default")]
public Cid CidDefault;
}
Imagine I've 3 fields and all are of type Cid. I've globally registered TypeConvertor CidHexaConvertor. It seems TypeConvertor attribute is ignored on attributes itself and is invoked only when define on the class/model itself. CidHexaConvertor has method to convert string to Cid and Cid to string. I can share more code later, but it seems attributes like this are not possible. Any clue?
Checking for [TypeConverter(typeof(...))] attributes applied to members is not implemented out of the box in Json.NET. You could, however, create a custom JsonConverter that wraps an arbitrary TypeConverter, then apply that to your model using JsonConverterAttribute.
First, define the following JsonConverter:
public class TypeConverterJsonConverter : JsonConverter
{
readonly TypeConverter converter;
public TypeConverterJsonConverter(Type typeConverterType) : this((TypeConverter)Activator.CreateInstance(typeConverterType)) { }
public TypeConverterJsonConverter(TypeConverter converter)
{
if (converter == null)
throw new ArgumentNullException();
this.converter = converter;
}
public override bool CanConvert(Type objectType)
{
return converter.CanConvertFrom(typeof(string)) && converter.CanConvertTo(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var tokenType = reader.SkipComments().TokenType;
if (tokenType == JsonToken.Null)
return null;
if (!tokenType.IsPrimitive())
throw new JsonSerializationException(string.Format("Token {0} is not primitive.", tokenType));
var s = (string)JToken.Load(reader);
return converter.ConvertFrom(null, CultureInfo.InvariantCulture, s);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var s = converter.ConvertToInvariantString(value);
writer.WriteValue(s);
}
}
public static partial class JsonExtensions
{
public static JsonReader SkipComments(this JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
public static bool IsPrimitive(this JsonToken tokenType)
{
switch (tokenType)
{
case JsonToken.Integer:
case JsonToken.Float:
case JsonToken.String:
case JsonToken.Boolean:
case JsonToken.Undefined:
case JsonToken.Null:
case JsonToken.Date:
case JsonToken.Bytes:
return true;
default:
return false;
}
}
}
Then apply it to your model as follows:
public class JsonModel
{
[JsonConverter(typeof(TypeConverterJsonConverter), typeof(CidNumberConvertor))]
[TypeConverter(typeof(CidNumberConvertor))]
[JsonProperty("cid_number")]
public Cid CidNumber;
[JsonConverter(typeof(TypeConverterJsonConverter), typeof(CidHexaConvertor))]
[TypeConverter(typeof(CidHexaConvertor))]
[JsonProperty("cid_hexa")]
public Cid CidHexa;
[JsonProperty("cid_default")]
public Cid CidDefault;
}
Notes:
Applying a JsonConverter overrides use of the global default TypeConverter for Cid.
The JsonConverterAttribute(Type,Object[]) constructor is used to pass the specific TypeConverter type to the constructor of TypeConverterJsonConverter as an argument.
In production code, I assume those are properties not fields.
Sample fiddle #1 here. (In the absence of a mcve I had to create a stub implementation of Cid.)
Alternatively, if you have many properties for which you want to use an applied TypeConverter when serializing to JSON, you can create a custom ContractResolver that instantiates and applies TypeConverterJsonConverter automatically:
public class PropertyTypeConverterContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.Converter == null)
{
// Can more than one TypeConverterAttribute be applied to a given member? If so,
// what should we do?
var attr = property.AttributeProvider.GetAttributes(typeof(TypeConverterAttribute), false)
.OfType<TypeConverterAttribute>()
.SingleOrDefault();
if (attr != null)
{
var typeConverterType = GetTypeFromName(attr.ConverterTypeName, member.DeclaringType.Assembly);
if (typeConverterType != null)
{
var jsonConverter = new TypeConverterJsonConverter(typeConverterType);
if (jsonConverter.CanConvert(property.PropertyType))
{
property.Converter = jsonConverter;
// MemberConverter is obsolete or removed in later versions of Json.NET but
// MUST be set identically to Converter in earlier versions.
property.MemberConverter = jsonConverter;
}
}
}
}
return property;
}
static Type GetTypeFromName(string typeName, Assembly declaringAssembly)
{
// Adapted from https://referencesource.microsoft.com/#System/compmod/system/componentmodel/PropertyDescriptor.cs,1c1ca94869d17fff
if (string.IsNullOrEmpty(typeName))
{
return null;
}
Type typeFromGetType = Type.GetType(typeName);
Type typeFromComponent = null;
if (declaringAssembly != null)
{
if ((typeFromGetType == null) ||
(declaringAssembly.FullName.Equals(typeFromGetType.Assembly.FullName)))
{
int comma = typeName.IndexOf(',');
if (comma != -1)
typeName = typeName.Substring(0, comma);
typeFromComponent = declaringAssembly.GetType(typeName);
}
}
return typeFromComponent ?? typeFromGetType;
}
}
Then use it as follows:
// Cache statically for best performance.
var resolver = new PropertyTypeConverterContractResolver();
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
var json = JsonConvert.SerializeObject(root, Formatting.Indented, settings);
var root2 = JsonConvert.DeserializeObject<JsonModel>(json, settings);
Notes:
You may want to cache the contract resolver for best performance.
JsonProperty.MemberConverter is obsolete in the current version of Json.NET but must be set identically to JsonProperty.Converter in earlier versions.
Sample fiddle #2 here.
My application is consuming an API, and I'm trying to deserialize data of images coming back. The data is formatted like:
{
"images":{
"totalCount":4,
"0":{
"url":"file1.jpg"
},
"1":{
"url":"file2.jpg"
},
"2":{
"url":"file3.jpg"
},
"3":{
"url":"file4.jpg"
}
}
}
I have these model classes:
public class MyViewModel
{
[JsonProperty("images")]
public ImagesViewModel Images { get; set; }
}
public class ImagesViewModel
{
[JsonProperty("totalCount")]
public int TotalCount { get; set; }
public Dictionary<string, ImageViewModel> ListImages { get; set; }
}
public class ImageViewModel
{
[JsonProperty("url")]
public string Url { get; set; }
}
The collection of images isn't really a collection, for some reason it's just a new property for each image. I'm trying to deserialize my object like:
... // create HttpClient object, add headers and such
System.Net.Http.HttpResponseMessage response = await
client.GetAsync(endpointUrl);
var jsonString = response.Content.ReadAsStringAsync();
MyViewModel model =
JsonConvert.DeserializeObject<MyViewModel>(jsonString.Result);
I get back the totalCount property just fine, but the collection of images is coming back null.
Is there a way for me to change my view models so that I can deserialize the json correctly?
Given the formatting of the JSON you will have to go the long route and try to deserialize it using JObjects
//using Newtonsoft.Json.Linq
var jObject = JObject.Parse(jsonString);
var images = jObject.Property("images").Value<JObject>(); ;
var viewModel = new MyViewModel {
Images = new ImagesViewModel {
TotalCount = images.Property("totalCount").Value<int>(),
ListImages = images.Properties().Skip(1).ToDictionary(p => p.Name, p => p.Value<ImageViewModel>())
}
};
Going a step further and using a JsonConverter for converting the payload itself actually works as well given that we know now how to convert it.
public class MyViewModelConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
return objectType == typeof(MyViewModel);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
var jObject = JObject.Load(reader);//<-- Note the use of Load() instead of Parse()
var images = jObject.Property("images").Value<JObject>(); ;
var model = new MyViewModel {
Images = new ImagesViewModel {
TotalCount = images.Property("totalCount").Value<int>(),
ListImages = images.Properties().Skip(1).ToDictionary(p => p.Name, p => p.Value<ImageViewModel>())
}
};
return model;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
throw new NotImplementedException();
}
}
and decorating the class itself
[JsonConverter(typeof(MyViewModelConverter))]
public class MyViewModel {
[JsonProperty("images")]
public ImagesViewModel Images { get; set; }
}
Deserialization is now as you intended to do before
var jsonString = await response.Content.ReadAsStringAsync();
MyViewModel model = JsonConvert.DeserializeObject<MyViewModel>(jsonString);
.NET Abhors dynamic types. They fly in the face of solid type checking at compile time. That being said, there is support for it:
As the example data is basically just a array of images, any collection could deal with this input.
If you can not even define the types umanbigiously (you might have a array of images and one of strings), the only way is ExpandoObject. It is designed specifically to deal with such cases. It is basically a List[string, object] with some Syntax Sugar, but it also does includes functions like Property Change Notifications.
Sounds like a job for a custom converter!
A custom converter will let you supply your own logic for deserializing specific types. Newtonsoft uses the target class to figure out with type if expects to find in the json and call the appropriate converter.
class ImagesViewModelConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ImagesViewModel);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
assertToken(JsonToken.StartObject);
var obj = new ImagesViewModel()
{
ListImages = new Dictionary<string, ImageViewModel>()
};
while (reader.Read() && reader.TokenType != JsonToken.EndObject)
{
assertToken(JsonToken.PropertyName);
var propName = (string)reader.Value;
if (propName.Equals(nameof(ImagesViewModel.TotalCount), StringComparison.InvariantCultureIgnoreCase))
{
reader.Read();
assertToken(JsonToken.Integer);
obj.TotalCount = (int)((Int64)reader.Value);
continue;
}
reader.Read();
var image = serializer.Deserialize<ImageViewModel>(reader); // you can still use normal json deseralization inside a converter
obj.ListImages.Add(propName, image);
}
return obj;
void assertToken(JsonToken token)
{
if (reader.TokenType != token)
throw new Exception(); // might wanna add detailed errors
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException(); // implement if needed
}
}
And then:
var settings = new JsonSerializerSettings()
{
Converters = new[] { new ImagesViewModelConverter() }
};
var obj = JsonConvert.DeserializeObject<MyViewModel>(jsonString, settings);
});
You can even change classes to be easier to handle, given that they no longer need to match the json exactly. You can for example replace the dict with an array and have the converter fill it in order.
I have a class:
public class CustomResponse
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string Message {get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string Details {get; set; }
}
Then I'm trying to deserialize JSON string to this class:
var settings = new JsonSerializerSettings
{
NullValueHandling.Ignore,
MissingMemberHandling.Ignore,
};
var customResponse = JsonConvert.Deserialize<CustomResponse>(jsonString, settings);
My JSON string for example:
{"DocumentId":"123321", "DocumentNumber":"ABC123"}
As a result I have an object which have all properties is NULL, but customResponse is not NULL. How do I get NULL in result?
If you want to avoid creating a custom JsonConverter, you can use the following extension method:
public static class Exts
{
public static T OrDefaultWhen<T>(this T obj, Func<T, bool> predicate)
{
return predicate(obj) ? default(T) : obj;
}
}
Usage:
var jsonString = #"{
Message: null,
Details: null
}";
var res = JsonConvert.DeserializeObject<CustomResponse>(jsonString)
.OrDefaultWhen(x => x.Details == null && x.Message == null);
You could create a custom JsonConverter that allocates and populates your object, then checks afterwards to see whether all properties are null, using the value returned by the JsonProperty.ValueProvider:
public class ReturnNullConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
// Get the contract.
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
// Allocate the object (it must have a parameterless constructor)
existingValue = existingValue ?? contract.DefaultCreator();
// Populate the values.
serializer.Populate(reader, existingValue);
// This checks for all properties being null. Value types are never null, however the question
// doesn't specify a requirement in such a case. An alternative would be to check equality
// with p.DefaultValue
// http://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_Serialization_JsonProperty_DefaultValue.htm
if (contract.Properties
.Where(p => p.Readable)
.All(p => object.ReferenceEquals(p.ValueProvider.GetValue(existingValue), null)))
return null;
return existingValue;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then use it like:
var settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore,
Converters = { new ReturnNullConverter<CustomResponse>() },
};
var customResponse = JsonConvert.DeserializeObject<CustomResponse>(jsonString, settings);
Sample fiddle.
However, in comments you wrote, if CustomResponse is NULL, then service return a correct response and i try to deserialize to OtherCustomResponse. In that case, consider using a polymorphic converter such as the ones from How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects? or Deserializing polymorphic json classes without type information using json.net