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.
Related
I'd like deserialization to fail for the following model:
class ExampleModel
{
public ExampleEnum ExampleEnum { get; set; }
public string ExampleString { get; set; }
}
enum ExampleEnum
{
Value1,
Value2,
}
when the ExampleEnum's value is not explicitly specified, i.e.:
{
"ExampleString " : "abc"
}
It seems that it falls back to the default enum's value by, well, default:
string json = "{ \"exampleString\" : \"abc\" }";
var model = JsonSerializer.Deserialize<ExampleModel>(json);
Console.WriteLine(model.ExampleEnum); // outputs "Value1"
Is it possible to change the behavior?
Unfortunately I didn't find a easy way to do it. Unlike newtonsoft.json , system.text.json does not has this feature built in. The only way seems to be to write a custom converter.
Here is how your custom converter code should look like.
public class ExampleModelRequiredPropertyConverter : JsonConverter<ExampleModel>
{
public override ExampleModel Read(ref Utf8JsonReader reader,Type type,JsonSerializerOptions options)
{
ExampleModel model = JsonSerializer.Deserialize<ExampleModel>(ref reader);
if (model.ExampleEnum == default)
{
throw new JsonException("Required property not received in the JSON");
}
return model;
}
public override void Write(Utf8JsonWriter writer,ExampleModel model, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, model);
}
}
After defining the converter you need to add the converter to the JsonSerializerOptions.Converters collection so that it gets registered. You cannot register this converter as an attribute as that will result in Stack Overflow exception.
If you want to use the converter as an attribute these is an alternate approach defined here in the Microsoft docs. This doc also talks about the above mentioned approach.
Let's say I've the following dynamic object:
public class SomeDynamicObject : DynamicObject
{
public string Text { get; set; }
}
If I serialize it using JsonConvert.SerializeObject(new SomeDynamicObject { Text = "hello world" }) it'll return {} instead of { "Text": "hello world" }.
I suspect the issue is that JSON.NET thinks it's a full dynamic object while my case is a dynamic object with declared members.
Is there any serialization settings or built-in converter that could be configured so JSON.NET can serialize both kinds of members?
To avoid confusion
Actual use case: I don't know which will be the types being serialized but I need to cover the whole use case of serializing declared properties of a dynamic object.
That is, I can't use attributes. That's why I'm asking if there's some converter or a serialization setting that can generalize this use case.
Update for non-attribute converter
Since you can't decorate, you lose a lot of power. Once the JsonWriter has converted to a JObject, the dynamic properties appear to be lost.
However, you can always use a little reflection in a custom converter's WriteJson method to serialize non-dynamic types.
public class SomeDynamicObject : DynamicObject
{
public string Text { get; set; }
public DynamicObject DynamicProperty { get; set; }
}
public class CustomDynamicConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
JObject jObject = JObject.Load(reader);
var target = Activator.CreateInstance(objectType);
//Create a new reader for this jObject, and set all properties to match the original reader.
JsonReader jObjectReader = jObject.CreateReader();
jObjectReader.Culture = reader.Culture;
jObjectReader.DateParseHandling = reader.DateParseHandling;
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
// Populate the object properties
serializer.Populate(jObjectReader, target);
return target;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var properties = value.GetType().GetProperties().Where(x => x.PropertyType != typeof(DynamicObject)).ToList();
JObject o = (JObject)JToken.FromObject(value);
properties.ForEach(x =>
{
o.AddFirst(new JProperty(x.Name, x.GetValue(value)));
});
o.WriteTo(writer);
}
}
If you explicitly decorate your properties with [JsonProperty], the serializer will pick them up, even if the containing type is dynamic.
public class SomeDynamicObject : DynamicObject
{
[JsonProperty]
public string Text { get; set; }
}
when serialized correctly outputs:
{"Text":"hello world"}
I have a custom JsonConverter for DateTimeOffset properties in my ViewModels. I have 100+ ViewModels.
public class ItemViewModel
{
public string Name { get; set; }
[JsonConverter(typeof(CustomDateTimeOffsetConverter))]
public DateTimeOffset DateCreated { get; set; }
}
How can I apply this attribute to all DateTimeOffset properties, without adding it to all my ViewModels?
I thought I had the solution when I read this answer, but when I apply it, the CustomResolver only fires on the parent object itself, and not the DateTimeOffset property, or any property.
public class CustomResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
JsonObjectContract contract = base.CreateObjectContract(objectType);
if (objectType == typeof(DateTimeOffset))
{
contract.Converter = new CustomDateTimeOffsetConverter();
}
return contract;
}
}
So to recap, I have everything else working. If I add the [JsonConverter(typeof(CustomDateTimeOffsetConverter))] attribute manually, then my application works like a charm. I am only asking how to add the attribute automatically, instead of manually.
You need to add the converter to the JsonSerializerSettings.Converters passed to the serializer.
Your JsonSerializerSettings will look as follows:
var settings = new JsonSerializerSettings()
{
Converters =
{
new CustomDateTimeOffsetConverter()
}
};
Your custom converter should also advertise it can convert the DateTimeOffset type with the following override of the JsonConverter method:
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(DateTimeOffset));
}
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.
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.