Serialization of ArgumentNullException to Json - c#

When serializing a System.ArgumentNullException to JSON using JSON.NET, part of the message appears lost:
var json = JsonConvert.SerializeObject(new ArgumentNullException("argument", "Some Message"));
returns:
{
"ClassName":"System.ArgumentNullException",
"Message":"Some Message",
"Data":null,
"InnerException":null,
"HelpURL":null,
"StackTraceString":null,
"RemoteStackTraceString":null,
"RemoteStackIndex":0,
"ExceptionMethod":null,
"HResult":-2147467261,
"Source":null,
"WatsonBuckets":null
}
I expected the Message token to be:
"Some Message\r\nParameter name: argument"
As noted by codefuller, the ArgumentNullException implements ISerializable and is the culprit. I can disable this by using custom JsonSerializerSettings.
However, the application needs to serialize any objects, including objects with an ArgumentNullException property. I'd prefer to avoid ignoring ISerialiable unless it presents a problem.
Both of my solutions include a custom JsonConverter.
Solution 1: Centralized JsonSerializer
Create a static list of JsonConverters to add to JsonSerializer.Converters
Include an ExceptionConverter that handles ArgumentNullException explicitly
Everywhere I need to serialize, ensure my JsonSerializer includes these converters
Cons: other developers must rigorously ensure they are including these converters with any serialization they do.
Solution 2: Override ArgumentNullException
I can create a CustomArgumentNullException class, and tag it with a JsonConvert attribute to leverage my custom converter.
Cons: other developers must rigorously ensure they are raising a CustomArgumentNullException instead of an ArgumentNullExcpetion.
Wished-for Solution
I'd love to somehow tag the existing ArgumentNullException with a JsonConverter attribute pointing to my custom converter. Is there some way to achieve this in the Json.NET code base?

Exception classes implement ISerializable interface. By the default for such objects Json.Net uses ISerializable.GetObjectData() method for data serialization. In other words ArgumentNullException class controls by itself how its data will be serialized. And somewhere inside it has a logic that serializes only actual message that was passed to constructor without Parameter name: argument part.
You could change this Json.Net behavior and avoid using of GetObjectData() with following JsonSerializerSettings:
var settings = new JsonSerializerSettings()
{
ContractResolver = new DefaultContractResolver()
{
IgnoreSerializableInterface = true,
},
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var json = JsonConvert.SerializeObject(new ArgumentNullException("argument", "Some Message"), settings);
However in this case set of serialized fields will differ from original json. Check whether it actually suits your needs.

Related

How to force System.Text.Json serializer throw exception when property is missing?

Json.NET behaviour could be defined by attributes: either use default or just throw an exception if json payload does not contain required property.
Yet System.Text.Json serializer silently does nothing.
Having class:
public sealed class Foo
{
[Required]
public int Prop {get;set;} = 10;
}
and deserializing empty object:
JsonSerializer.Deserialize<Foo>("{}");
I simply get an instance of Foo with Prop=10.
I could not find any setting in JsonSerializerOptions to force it throw an exception. Is it possible?
System.Text.Json doesn't throw an exception if no value is received for one of the properties of the target type. You need to implement a custom converter.
Reference: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#required-properties
System.Text.Json isn't a full replacement for Json.NET. It's built for speed with minimal allocations in mind, not feature completeness. If you want validations you can
Use Json.NET
Validate the objects after serialisation with the Validator class
Create a custom converter
TheGeneral showed how to do #3. A custom validator would have to handle all validations explicitly and return some meaningful exception though. Throwing an ArgumentNullException is enough if there's only a single property to check. Validating multiple properties would require something more complex like a ValidationException to include the validation results.
K. Scott Allen's article Manual Validation with Data Annotations shows how to do #2.
One option is to use the Validator.ValidateObject to validate an object and get a ValidationException with all the failing validations :
try
{
var validationCtx=new ValidationContexgt(myFoo);
Validator.ValidateObject(myFoo,validationCtx);
}
catch(ValidatinException ex)
{
//Do something with the results.
}
This is OK if invalid objects are rare, as throwing exceptions is expensive. Perhaps a better option is to use Validator.TryValidateObject :
var results = new List<ValidationResult>();
var validationCtx=new ValidationContexgt(myFoo);
if(Validator.TryValidateObject(myFoo,validationCtx,results))
{
//Use the object
}
else
{
//Do something with the failed results
}
All you need to do SetMissingMemberHandling and it will handle every thing for you but you need to install DevBetter.JsonExtensions
MissingMemberHandling.Ignore and MissingMemberHandling.Error
var deserializeOptions = new JsonSerializerOptions()
.SetMissingMemberHandling(MissingMemberHandling.Ignore);
var weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString, deserializeOptions);

Constructor of a XmlSerializer serializing a List<T> throws an InvalidOperationException when used with XmlAttributeOverrides

Summary
When using the XmlSerializer class, serializing a List<T> (where T can be serialized with XmlSerializer without problems) using XmlAttributeOverrides such as this:
using xmls = System.Xml.Serialization;
...
xmls.XmlAttributeOverrides attributeOverrides = new xmls.XmlAttributeOverrides();
attributeOverrides.Add(typeof(T), new xmls.XmlAttributes()
{
XmlRoot = new xmls.XmlRootAttribute("foo")
});
attributeOverrides.Add(typeof(List<T>), new xmls.XmlAttributes()
{
XmlArray = new xmls.XmlArrayAttribute("foobar"),
XmlArrayItems = { new xmls.XmlArrayItemAttribute("foo") },
});
will throw the following InvalidOperationExcpetion at the inner-most exception:
System.InvalidOperationException: XmlRoot and XmlType attributes may not be specified for the type System.Collections.Generic.List`1[[T, programname, Version=versionnumber, Culture=neutral, PublicKeyToken=null]].
What I expect from the serializer
<texparams>
<texparam pname="TextureMinFilter" value="9729"/>
<texparam pname="TextureMagFilter" value="9729"/>
</texparams>
What I can sucessfully get at the moment
<ArrayOfTextureParameter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<TextureParameter pname="TextureMinFilter" value="9729" />
<TextureParameter pname="TextureMagFilter" value="9728" />
</ArrayOfTextureParameter>
Background Info
I have been messing around with XML Serialization lately but have come across a problem. I am trying to serialize and deserialize some classes that wrap OpenGL textures.
In one of my classes (which I appropriately called BitmapTexture2d) I have a Bitmap field, which I wish to store in a Base64 element like so:
<bitmap64>
*base64goeshere*
</bitmap64>
Since I want to keep my code as neat as possible I decided to use the IXmlSerializable interface instead of creating a property which can convert a string and a Bitmap back and forth.
Later on in the process I decided to use the XmlSerializer class to generate the XML for a single field defined in Texture2d (which BitmapTexture2d is derived from) called Parameters (which is a List<TextureParameter> and TextureParameter is serializable by the XmlSerialization class). However this is how the serializer defaulted to serializing the List<TextureParameter>:
<ArrayOfTextureParameter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<TextureParameter pname="TextureMinFilter" value="9729" />
<TextureParameter pname="TextureMagFilter" value="9728" />
</ArrayOfTextureParameter>
After seeing this, I decided to try and change the names of the nodes. After some research (where I landed at stackoverflow multiple times) I discovered the XmlAttributeOverrides class which can be passed to the constructor of XmlSerializer to add/override node names etc.
After writing out my code, the constructor for the sub-serializer started throwing an exception as described above. I have tried using an array which threw the same exception. I later though to serialize each element in the list one by one, but came to the conclusion that it was harder than I thought to achieve it that way. I posted this question somewhere else with no answer. And here I am...
Your problem is that you are trying to use overrides to attach [XmlArray] and [XmlArrayItem] to the type List<T>. However, as shown in the docs, [XmlArray] cannot be used in this manner:
[AttributeUsageAttribute(AttributeTargets.Property | AttributeTargets.Field
| AttributeTargets.Parameter | AttributeTargets.ReturnValue,
AllowMultiple = false)]
public class XmlArrayAttribute : Attribute
Notice there is no AttributeTargets.Class included? That means that [XmlArray] cannot be applied directly to a type and so attempting to do so via XML overrides rightly throws an exception.
But as to why that exception message states,
System.InvalidOperationException: XmlRoot and XmlType attributes may not be specified for the type System.Collections.Generic.List`1...
Er, well, that message is simply wrong. It would appear to be a minor bug in XmlSerializer. You could even report it to Microsoft if you want.
What you need to do instead is:
Override the [XmlRoot] attribute of List<T> to specify the desired name, in this case "texparams", AND
Override the [XmlType] attribute of T and set XmlTypeAttribute.TypeName to be the desired collection element name. In the absence of an [XmlArrayItem(name)] override this is what controls the element names of collections whose items are of type T.
Thus your code should look like:
static XmlSerializer MakeListSerializer<T>(string rootName, string elementName)
{
xmls.XmlAttributeOverrides attributeOverrides = new xmls.XmlAttributeOverrides();
attributeOverrides.Add(typeof(List<T>), new xmls.XmlAttributes()
{
XmlRoot = new xmls.XmlRootAttribute(rootName),
});
attributeOverrides.Add(typeof(T), new xmls.XmlAttributes()
{
XmlType = new xmls.XmlTypeAttribute(elementName),
});
return new XmlSerializer(typeof(List<T>), attributeOverrides);
}
Sample fiddle.
Note that when constructing an XmlSerializer using XmlAttributeOverrides you must cache the serializer for later reuse to avoid a severe memory leak, for reasons explained here.

Json.Net Deserialize to JToken with Custom Contract Resolver

JSON.Net works well if I give a custom generic type to Deserialize method. But, when I change the output object type as JToken, it does not finish the deserialization process through given serializer setting.
I must notify the deserializer that put this property into that model property (e.g. => the property will be deserialized: "date_add" will be: "DateAdd" in the model). I do not want to use attributes.
When I debug my code below, CustomContractResolver does not work even I put the breakpoint into there.
Here is my code:
var model = JsonConvert.DeserializeObject<JToken>(content,
new JsonSerializerSettings() {ContractResolver = new CustomContractResolver()});

JSon.Net how to have $type only for deserialization

I used the $type properties in Json for the deserialization of inherited objects (to choose the correct object type in which to deserialize at runtime)
I do this using :
config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings{ TypeNameHandling = TypeNameHandling.Objects}
this line is in the WebApiConfig file.
It works fine but I can't figure out how to restrict the $type only to the webservices who actually needs it (I only need it for one PUT and one POST).
The $type is messing up the API results for the other webservices and I can't find some TypeNameHandling configuration nor some serialization option to avoid this.
Does anyone knows how to do that?
Cheers!
To enable type name handling on a nested object, you can attach [JsonProperty(ItemTypeNameHandling = TypeNameHandling.Objects)] to the containing property, like so:
public class RootObject
{
[JsonProperty(ItemTypeNameHandling = TypeNameHandling.Objects)]
public object Data { get; set; }
}
There's no builtin way to enable type name handling on a specific type, and so there's no builtin way to enable type name handling on the root object. Instead, you could use the EnableJsonTypeNameHandlingConverter from SignalR Typenamehandling if you need to do this.
Thanks for the help dbc. I had other issues during deserialization (using a OData.Delta object and the solution you proposed didn't actually fit completely the need.
I overcame the issue thanks to this post : Custom Json.NET serializer settings per type
to have the $type customization only for a few webservices.

Deserialize JSON to C# collection

I am using Newtonsoft.Json to serialize/deserialize my C# object to/from JSON.
My C# object has a property of following type:
List<BaseClass> objects { get; set; }
This collection holds different child objects (e.g. - ChildClass1, ChildClass2).
Serializing to JSON works fine but while deserializing it creates objects of BaseClass in collection (which is obvious :)).
Is there a way I can create concrete child objects while deserializing?
I tried creating a custom converter by implementing CustomCreationConverter<BaseClass>. But the problem is overridden Create method gives me just the type and based on that I cannot decide which object to return (ChildClass1 or ChildClass2)
My JSON string has a Type property by which I can identify the child object to create.
Any help is highly appreciated.
You can use the TypeNameHandling option:
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }
JsonConvert.SerializeObject(myObject, Formatting.Indented, settings );
The TypeNameHandling option "Auto" ensures that type names are included in the serialized JSON
for subclasses. But watch out: these names become invalid when the classes or their namespaces are renamed!

Categories