I'm deserializing an object using Json.NET that contains a private field of type Guid and a public property for that field. When the value for my Guid is null in my json I want to assign Guid.Empty to my field.
public class MyClass
{
private Guid property;
public Guid Property
{
get { return property; }
set
{
if (value == null)
{
property = Guid.Empty;
}
else
{
property = value;
}
}
}
}
But the deserializer wants to access the private field, cause I get this error when I try to deserialize:
Error converting value {null} to type 'System.Guid'. Path
'[0].property', line 6, position 26.
How can I make it ignore the private field and use the public property instead?
Json.NET refuses to set a null value for a Guid because it is a non-nullable value type. Try typing (Guid)null in the Immediate Window and you will see an error message indicating that this conversion cannot be made in .Net.
To work around this, you have a couple of options:
Create a Guid? nullable proxy property. It can be private if you desire as long as it has a [JsonProperty] attribute:
public class MyClass
{
[JsonIgnore]
public Guid Property { get; set; }
[JsonProperty("Property")]
Guid? NullableProperty { get { return Property == Guid.Empty ? null : (Guid?)Property; } set { Property = (value == null ? Guid.Empty : value.Value); } }
}
Create a JsonConverter that converts a null Json token to a default Guid value:
public class NullToDefaultConverter<T> : JsonConverter where T : struct
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(T);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
if (token == null || token.Type == JTokenType.Null)
return default(T);
return token.ToObject(objectType); // Deserialize using default serializer
}
// Return false instead if you don't want default values to be written as null
public override bool CanWrite { get { return true; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (EqualityComparer<T>.Default.Equals((T)value, default(T)))
writer.WriteNull();
else
writer.WriteValue(value);
}
}
Then apply it to your type as follows:
public class MyClass
{
[JsonConverter(typeof(NullToDefaultConverter<Guid>))]
public Guid Property { get; set; }
}
Alternatively, you can apply the converter to all values of type T by adding the converter to JsonSerializerSettings.Converters. And, to register such a converter globally, see e.g.How to set custom JsonSerializerSettings for Json.NET in MVC 4 Web API? for Web API, Setting JsonConvert.DefaultSettings asp net core 2.0 not working as expected for ASP.NET Core or Registering a custom JsonConverter globally in Json.Net for a console app.
If you do register the converter globally for a console app, you may need to disable it for recursive calls as shown in JSON.Net throws StackOverflowException when using [JsonConvert()].
If you only need to deserialize a null value for a Guid and not re-serialize it as such, you can apply [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] to the Guid property, and null values will ignored despite being invalid Guid values:
public class MyClass
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public Guid Property { get; set; }
}
Of course if you do this your Guid will be re-serialized as "00000000-0000-0000-0000-000000000000". To ameliorate that you could apply DefaultValueHandling = DefaultValueHandling.Ignore which will cause empty Guid values to be omitted during serialization:
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
public Guid Property { get; set; }
Note that if a parameterized constructor called during deserialization has a non-nullable Guid argument with the same name, a different approach may be required.
Related
I've been trying to deserialize some null paramaters with JsonSerializer and a custom JsonConverter from examples from posts I've seen about the subject, but its not working.
When I debug the NullToEmptyStringConverter is seems to be skipping the null parameter Version which I have deliberately set to null and just returning the string values that have a value. Then when you debug the deserialized object it is Version = null still.
public class NullToEmptyStringConverter : JsonConverter<string> {
public override bool CanConvert(Type typeToConvert) {
return typeToConvert == typeof(string);
}
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
string value = reader.GetString();
if(value == null) {
value = "";
}
return value;
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) {
throw new NotImplementedException();
}
}
I have added it to JsonSerializeOptions and added the attribute. Another thing I am confused about is why is the Read method being invoked for every parameter even though I have placed it above the Version property?
public class Application {
[JsonPropertyName("version")]
[JsonConverter(typeof(NullToEmptyStringConverter))]
public string Version { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("description")]
public string Description { get; set; }
}
Adding to the Converter
var options = new JsonSerializerOptions();
options.Converters.Add(new NullToEmptyStringConverter());
var config = JsonSerializer.Deserialize<ConfigRoot>(responseData.ToString(), options);
When I debug the NullToEmptyStringConverter is seems to be skipping the null parameter Version...
JsonConverter<T> includes a virtual property named HandleNull, which is false by default. Because of this, NullToEmptyStringConverter isn't used for null values. Override the getter and set it to return true, like this:
public override bool HandleNull => true;
...why is the Read method being invoked for every parameter even though I have placed it above the Version property?
In your example, you have the following line:
options.Converters.Add(new NullToEmptyStringConverter());
This registers NullToEmptyStringConverter as a global converter. Remove this registration and the converter will run only for the Version property, due to the [JsonConverter] attribute.
I have a custom JSON converter, because 3rd party API accept specific structure only.
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var bodyRequest = (IRequest) value;
writer.WriteStartObject();
writer.WritePropertyName(bodyRequest.RequestName);
writer.WriteStartObject();
writer.WritePropertyName("-xmlns");
writer.WriteValue("http://example.com");
writer.WritePropertyName(bodyRequest.WrapperName);
writer.WriteStartObject();
var contractResolver = new CamelCasePropertyNamesContractResolver {
IgnoreSerializableAttribute = false
};
var properties = value.GetType().GetProperties();
foreach(var propertyInfo in properties) {
if (HasAttribute(propertyInfo, typeof(JsonIgnoreAttribute))) {
continue;
}
var propValue = propertyInfo.GetValue(value);
var propertyName = contractResolver.GetResolvedPropertyName(propertyInfo.Name);
writer.WritePropertyName(propertyName);
writer.WriteValue(propValue);
}
writer.WriteEndObject();
writer.WriteEndObject();
writer.WriteEndObject();
}
So, I create strcuture with help of WriteXXX methods, then I get all properties and serialize them. All works fine, but I need to handle Enums. For example, we have the following request model:
public class ExampleRequest : IRequest
{
public long Id{ get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public CarType CarType { get; set; }
public string RequestName => "Request";
public string WrapperName => "SendData";
}
public enum CarType
{
[EnumMember(Value = "NEW_CAR")
New,
[EnumMember(Value = "OLD_CAR")
Old
}
Currently, after serialization I see that CarType has numeric value 0 or 1,I understand that I use reflection and StringEnumConverter is ignored. How to properly serialize in this case?
Inside your loop, check whether the property has a JsonConverterAttribute applied. If so, get the type of converter, instantiate it and then call its WriteJson method instead of writing the value directly. Otherwise, just write the value as you are doing now.
In other words, replace this line:
writer.WriteValue(propValue);
with this:
var att = propertyInfo.GetCustomAttribute<JsonConverterAttribute>();
if (att != null)
{
var converter = (JsonConverter)Activator.CreateInstance(att.ConverterType);
converter.WriteJson(writer, propValue, serializer);
}
else
{
writer.WriteValue(propValue);
}
I have a collection of name / value pairs where they are defined with the words name and value just like a Key/Value object, i.e.
[{"Name":"ActivityId","DataType":1,"Value":"a7868f8c-07ac-488d-a414-714527c2e76f"},
{"Name":"Address1","DataType":2,"Value":"123 Main St"}]
If I had an object like:
class Request
{
public Guid ActivityId { get; set; }
public string Address1 {get; set; }
}
How can I deserialize this to the class above?
Should I consider a custom converter? Does Json.NET have something built-in? Is there a way to decorate the properties with an attribute that I'm missing? Would it be easier to customize the serialization?
I'm trying to avoid pulling the data for each property from a Dictionary, which would be the easy route, but would require me to do this with each custom implementation. I would prefer to do this in a base class in a single method using Json.NET (or something in the .NET framework).
I've searched quite a bit, and most examples are real name/value pairs, not prefixed with name and value, i.e.
[{"ActivityId":"a7868f8c-07ac-488d-a414-714527c2e76f"}]
Any ideas?
This can be done in a straightforward manner with a custom JsonConverter like the one below. The converter works by first transforming the array of name-value pairs into a JObject with properties mirroring the pairs, then populating the target object from the JObject using the serializer's built-in Populate method.
class NameValueConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Load the array of name-value pairs and transform into a JObject.
// We are assuming all the names will be distinct.
JObject obj = new JObject(
JArray.Load(reader)
.Children<JObject>()
.Select(jo => new JProperty((string)jo["Name"], jo["Value"]))
);
// Instantiate the target object and populate it from the JObject.
object result = Activator.CreateInstance(objectType);
serializer.Populate(obj.CreateReader(), result);
return result;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// WriteJson is not called when CanWrite returns false
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
// We only want this converter to handle classes that are expressly
// marked with a [JsonConverter] attribute, so return false here.
// (CanConvert is not called when [JsonConverter] attribute is used.)
return false;
}
}
To use the converter, just add a [JsonConverter] attribute to the target class:
[JsonConverter(typeof(NameValueConverter))]
class Request
{
public Guid ActivityId { get; set; }
public string Address1 {get; set; }
}
Then, you can deserialize as you normally would:
Request req = JsonConvert.DeserializeObject<Request>(json);
Fiddle: https://dotnetfiddle.net/tAp1Py
I'm trying to come up with a pattern that can detect when a property has been set to null. Something similar to the Nullable<T> class, but a little more advanced. Let's call it MoreThanNullable<T>. Basically I need to do something different depending on the following 3 scenarios:
The property was never set
The property was set to null
The property is set to an instance of T.
I created my own class to do this with an "instantiated" property and it all works within a test scenario. Some sample code:
public struct MoreThanNullable<T>
{
private bool hasValue;
internal T value;
public bool Instantiated { get; set; }
public MoreThanNullable(T value)
{
this.value = value;
this.hasValue = true;
Instantiated = true;
}
// ... etc etc...
And the test that passes as I expect it to:
[TestFixture]
public class MoreThanNullableTests
{
public class AccountViewModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public MoreThanNullable<string> Test1 { get; set; }
public MoreThanNullable<string> Test2 { get; set; }
public MoreThanNullable<string> Test3 { get; set; }
}
[Test]
public void Tests()
{
var myClass = new AccountViewModel();
Assert.AreEqual(false, myClass.Test1.Instantiated);
myClass.Test1 = null;
Assert.AreEqual(true, myClass.Test1.Instantiated);
}
}
Next, using this same view model I wire it up to a POST on my REST service and I pass in the following JSON:
{
Name:"test",
Test1:null,
Test2:"test"
}
... and it all falls to pieces. Neither Test1 nor Test3 get instantiated! Obviously, I'm not expecting Test3 to get instantiated, but Test1 remaining uninstantiated was unexpected. Effectively I can't tell the difference between a property I did and didn't receive via REST. (Note: In our application there is a distinct difference between not set and set to null)
Am I doing someting wrong? Or is this correct behaviour for Web API?
** UPDATE **
What I probably didn't make clear is that I have effectively replicated the Nullable<T> class to achieve this, including overloading the implicit operator like this:
public static implicit operator MoreThanNullable<T>(T value)
{
return new MoreThanNullable<T>(value);
}
I always seem to leave out important stuff in my questions...
Why does it not work? In your JSON
{
Name:"test",
Test1:null,
Test2:"test"
}
Test1 end Test2 are primitive types but you created your own type and those are the properties it should map over to so its no longer primitive but a complex object. So really your JSON is expected to also contain objects. It would work if you changed it to
{
Name:"test",
Test1:{Instantiated:false, value: null},
Test2:{Instantiated:false, value: "test"},
}
as now they are objects and deserialized as such. Also your struct has a default empty constructor, if it were a class with a private default constructor it would not work again.
Now, how do you get a primitive type like a string to deserialize over to your custom type? You might be able to do this with a custom web api action filter and parse the incoming json and map it to your object in the OnActionExecuting.
EDIT 2
Assuming you are using JSON.NET from newtonsoft you can plug in a custom JSON converter. This would be a global change across the web api application, no need for custom filters.
public sealed class MoreThanNullableConverter : Newtonsoft.Json.JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof (MoreThanNullable<>);
}
public override bool CanWrite { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// return a new MoreThanNullable instance with value
return Activator.CreateInstance(objectType, reader.Value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Change to webapiconfig.cs
var formatters = GlobalConfiguration.Configuration.Formatters;
var jsonFormatter = formatters.JsonFormatter;
var settings = jsonFormatter.SerializerSettings;
jsonFormatter.SerializerSettings.Converters.Add(new MoreThanNullableConverter());
i think you have fallen in a common problem with deserialization on services in .net
I've always seen this problem with WCF, but i think it's also present in json deserialization
https://msdn.microsoft.com/en-us/library/system.runtime.serialization.ondeserializedattribute(v=vs.110).aspx
.NET during deserialization doesn't execute constructor, so if you need to execute some code after deserialization you have to add some code as these
[OnDeserialized()]
internal void OnDeserializedMethod(StreamingContext context)
{
// ... logic here after deserialization
}
In that logic you should complete private field with missing information.
hasValue field must setted depending if value is null or not.
Instantiated should have a private field with setter private. It should setted on true on public setter of value.
If hasValue is true, instantiated is obviously true, how can be otherwise?
So the problem is, how can we have hasvalue false but instatiated true?
This is the only case when you cannot determine the value with other values, so we need somthing else. Making the setter of instantiated public is the simplest way but for ensure object consistence you should check it after deserialization to ensure previous object rules...
I am logging all requests to my WCF web services, including the arguments, to the database. This is the way I do it:
create a class WcfMethodEntry which derives from PostSharp's aspect OnMethodBoundaryAspect,
annotate all WCF methods with WcfMethodEntry attribute,
in the WcfMethodEntry I serialize the method arguments to json with the JsonConvert.SerializeObject method and save it to the database.
This works ok, but sometimes the arguments are quite large, for example a custom class with a couple of byte arrays with photo, fingerprint etc. I would like to exclude all those byte array data types from serialization, what would be the best way to do it?
Example of a serialized json:
[
{
"SaveCommand":{
"Id":5,
"PersonalData":{
"GenderId":2,
"NationalityCode":"DEU",
"FirstName":"John",
"LastName":"Doe",
},
"BiometricAttachments":[
{
"BiometricAttachmentTypeId":1,
"Parameters":null,
"Content":"large Base64 encoded string"
}
]
}
}
]
Desired output:
[
{
"SaveCommand":{
"Id":5,
"PersonalData":{
"GenderId":2,
"NationalityCode":"DEU",
"FirstName":"John",
"LastName":"Doe",
},
"BiometricAttachments":[
{
"BiometricAttachmentTypeId":1,
"Parameters":null,
"Content":"..."
}
]
}
}
]
Edit: I can't change the classes that are used as arguments for web service methods - that also means that I cannot use JsonIgnore attribute.
The following allows you to exclude a specific data-type that you want excluded from the resulting json. It's quite simple to use and implement and was adapted from the link at the bottom.
You can use this as you cant alter the actual classes:
public class DynamicContractResolver : DefaultContractResolver
{
private Type _typeToIgnore;
public DynamicContractResolver(Type typeToIgnore)
{
_typeToIgnore = typeToIgnore;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
properties = properties.Where(p => p.PropertyType != _typeToIgnore).ToList();
return properties;
}
}
Usage and sample:
public class MyClass
{
public string Name { get; set; }
public byte[] MyBytes1 { get; set; }
public byte[] MyBytes2 { get; set; }
}
MyClass m = new MyClass
{
Name = "Test",
MyBytes1 = System.Text.Encoding.Default.GetBytes("Test1"),
MyBytes2 = System.Text.Encoding.Default.GetBytes("Test2")
};
JsonConvert.SerializeObject(m, Formatting.Indented, new JsonSerializerSettings { ContractResolver = new DynamicContractResolver(typeof(byte[])) });
Output:
{
"Name": "Test"
}
More information can be found here:
Reducing Serialized JSON Size
You could just use [JsonIgnore] for this specific property.
[JsonIgnore]
public Byte[] ByteArray { get; set; }
Otherwise you can also try this: Exclude property from serialization via custom attribute (json.net)
Another way would be to use a custom type converter and have it return null, so the property is there but it will simply be null.
For example i use this so i can serialize Exceptions:
/// <summary>
/// Exception have a TargetSite property which is a methodBase.
/// This is useless to serialize, and can cause huge strings and circular references - so this converter always returns null on that part.
/// </summary>
public class MethodBaseConverter : JsonConverter<MethodBase?>
{
public override void WriteJson(JsonWriter writer, MethodBase? value, JsonSerializer serializer)
{
// We always return null so we don't object cycle.
serializer.Serialize(writer, null);
}
public override MethodBase? ReadJson(JsonReader reader, Type objectType, MethodBase? existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
return null;
}
}
Try to use the JsonIgnore attribute.