This question already has an answer here:
Best way to store serialization related info between serialization and deserialization
(1 answer)
Closed 9 years ago.
class Packet
{
public Action{get;set;}
}
class Action
{
string Type;
}
class ConcreteAction1 : Action
{
base.Type="ConcreteAction1";
}
class ConcreteAction2 : Action
{
base.Type="ConcreteAction2";
}
I receive Packet class which contains various derived class from Action class, by default every ConcreteActions are deserialized as base class Action, but I want them to be deserialized as their actual types(ConcreteAction1 ,ConcreteAction2...)
Is there a way to achieve this?
Use .Parse() to parse it to an object and then cast, or use .DeserializeObject<T>() to deserialize it to your object. Then see if the return value is correct, if not try something else. Or try to use an identifier to see which of the concrete types it is and hook into the deserializer. But this might not be the most ideal solution.
Alternatively, if you are generating the JSON yourself, you can play with the settings.
new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }
When you serialize it, it will write the actual concrete type into the JSON and when you deserialize it will transform it into the concrete type.
JsonConvert.Deserialize(input, null, settings)
But, perhaps someone has a better idea. :)
Related
So I've been trying to convert JSON to an immutable object for an API, and I've just hit wall after wall with it. The System.Text.Json.Serialization.JsonConstructor attribute doesn't seem to work at all. Here's an example of the record.
using System.Text.Json.Serialization;
namespace App.Foobar{
public record Foo
{
[JsonConstructor]
public Foo(decimal bar){
Bar = bar;
}
[JsonIgnore]
public decimal Bar {get;}
}
}
And the JSON I'm passing in;
{
foo: {
bar: 12
}
}
So how would I deserialize JSON into this class? Is there something I'm doing wrong with the JSON I'm passing in, or is it a problem in the class?
I need to preserve the JsonIgnore attribute, as well.
Your problem is that the JsonIgnoreAttribute applies to both serialization and deserialization, and this is something you cannot change.
So in short, if you have [JsonIgnore], you won't be able to get its value from deserializing JSON data, no matter what.
As a workaround, you cuold play with read-only properties, or null-valued properties or even default-valued properties since you can set serialization options for these. See this article for more information.
In general, you can read about immutable types as Microsoft has envisioned them to make sure you don't try to do weird stuff.
AutoMapper Workaround
You can also work around this using AutoMapper. You can have a private record class that does not ignore the bar property and then map it to a public record class that does ignore said property.
And actually, I don't know if AutoMapper works with record. Test it out. If not, do the mapping yourself.
On our API we need to take in json, deserialize it to an interface, set a field, and ship it off. To achieve this, on both ends I'm setting the jsonConvert to use TypeNameHandling.All. The endpoint in question is supposed to be fairly locked down, but there's always a chance of someone gaining access and setting $type to a system class with a dangerous constructor or garbage collection method.
My question is would clarifying the namespace of the type before attempting to deserialize it be sufficiently safe? or would there still be a risk of having something like a sub-object with a dangerous class type in the json? If there is still a risk or an exploit I've missed, what other steps can I do to mitigate the danger?
Our company name is at the start of every namespace we use, so in the code below we just check that the type set in the json starts with our company name. The {} at the start is just so the compiler knows it doesn't need to keep the JObject in memory after the check.
{ //check the type is valid
var securityType = JsonConvert.DeserializeObject<JObject>(request.requestJson);
JToken type;
if (securityType.TryGetValue("$type", out type))
{
if (!type.ToString().ToLower().StartsWith("foo")) { //'foo' is our company name, all our namespaces start with foo
await logError($"Possible security violation, client tried to instantiate {type}", clientId: ClientId);
throw new Exception($"Request type {type} not supported, please use an IFoo");
}
}
else
{
throw new Exception("set a type...");
}
}
IFoo requestObject = JsonConvert.DeserializeObject<IFoo>(request.requestJson, new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
});
The risk with TypeNameHandling is that an attacker may trick the receiver into constructing an attack gadget - an instance of a type that when constructed, populated or disposed effects an attack on the receiving system. For an overview see
TypeNameHandling caution in Newtonsoft Json
External json vulnerable because of Json.Net TypeNameHandling auto?
If you are going to protect against such attacks by requiring all deserialized types to be in your own company's .Net namespace, be aware that, when serializing with TypeNameHandling.All, "$type" information will appear throughout the JSON token hierarchy, for all arrays and objects (including for .Net types such as List<T>). As such you must needs apply your "$type" check everywhere type information might occur. The easiest way to do this is with a custom serialization binder such as the following:
public class MySerializationBinder : DefaultSerializationBinder
{
const string MyNamespace = "foo"; //'foo' is our company name, all our namespaces start with foo
public override Type BindToType(string assemblyName, string typeName)
{
if (!typeName.StartsWith(MyNamespace, StringComparison.OrdinalIgnoreCase))
throw new JsonSerializationException($"Request type {typeName} not supported, please use an IFoo");
var type = base.BindToType(assemblyName, typeName);
return type;
}
}
Which can be used as follows:
var settings = new JsonSerializerSettings
{
SerializationBinder = new MySerializationBinder(),
TypeNameHandling = TypeNameHandling.All,
};
This has the added advantage of being more performant than your solution since pre-loading into a JObject is no longer required.
However, having done so, you may encounter the following issues:
Even if the root object is always from your company's namespace, the "$type" properties for nested values may not necessarily be in your companies namespace. Specifically, type information for harmless generic system collections such as List<T> and Dictionary<TKey, Value> as well as arrays will be included. You may need to enhance BindToType() to whitelist such types.
Serializing with TypeNameHandling.Objects or TypeNameHandling.Auto can ameliorate the need to whitelist such harmless system types, as type information for such system types is less likely to get included during serialization as compared to TypeNameHandling.All.
To further simplify the type checking as well as to reduce your attack surface overall, you might consider only allowing type information on the root object. To do that, see json.net - how to add property $type ONLY on root object. SuppressItemTypeNameContractResolver from the accepted answer can be used on the receiving side as well as the sending side, to ignore type information on non-root objects.
Alternatively, you could serialize and deserialize with TypeNameHandling.None globally and wrap your root object in a container marked with [JsonProperty(TypeNameHandling = TypeNameHandling.Auto)] like so:
public class Root<TBase>
{
[JsonProperty(TypeNameHandling = TypeNameHandling.Auto)]
public TBase Data { get; set; }
}
Since your root objects all seem to implement some interface IFoo you would serialize and deserialize a Root<IFoo> which would restrict the space of possible attack gadgets to classes implementing IFoo -- a much smaller attack surface.
Demo fiddle here.
When deserializing generics, both the outer generic and the inner generic parameter types may need to be sanitized recursively. For instance, if your namespace contains a Generic<T> then checking that the typeName begins with your company's namespace will not protect against an attack via a Generic<SomeAttackGadget>.
Even if you only allow types from your own namespace, it's hard to say that's enough to be sufficiently safe, because we don't know whether any of the classes in your own namespace might be repurposed as attack gadgets.
TLDR Question:
How to cast a string to a specific type of object?
Long Question
I want to make a PlayerPrefs wrapper where I can store whatever data I want.
so it goes like this
void Set<T>(string Key, T Value)
{
PlayerPrefs.SetString(Key, Value.ToString());
}
T Get<T>(string Key)// where T : IParseable
{
//Code that checks for errors and throws exceptions
return T.Parse(PlayerPrefs.GetString(Key));
}
The problem in this is that it "relies" on the data to be parseable (or implement IParseable that I invented XD) and primitive data types don't implement it even thought they all have a Parse Method
Is there already an IParseable interface I can use?
If not, is there a way to know if the T type is a primitive data type?
Is there a better way to achieve what I want to do?
Would it be better if I used JSON for this?
The comments are correct, you shouldn't be using ToString() and Parse. What you are doing (or trying to do) is serialization. Here are a couple of resources to look to for figuring out how to serialize and deserialize your data:
Serialize an object to string
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-serialize-and-deserialize-json-data
If you use one of the above examples, the data should be serialized to a human-readable format. It's less efficient, but serializing to a string is already less efficient than serializing to bytes. If you need that level of efficiency, you shouldn't be using PlayerPrefs.
I've a concrete class below which inherits from abstract class:
[Serializable]
public class MyConcreteClass : MyAbstractClass
{
public string MyProperty { get; set; }
}
[Serializable]
public abstract class MyAbstractClass { }
NewtonSoft JSON Serializer throws exception below when trying to de/serialize MyconcreteClass class:
Newtonsoft.Json.JsonSerializationException: Could not create an
instance of type MyAbstractClass. Type is an interface or abstract
class and cannot be instantiated. Path ....
Did a bit of googling and found this setting below:
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
};
If I use above setting i.e. TypeNameHandling.All, the error goes away.
Questions in my mind:
Is this is the correct approach to fix this issue (And not sure what this option is not out of box)
Any performance or negative impacts that I should be aware of with this setting.
Thanks.
1. Is this is the correct approach to fix this issue (And not sure what this option is not out of box)
I think it's correct approach to to de/serialize inheritance class with NewtonSoft JSON. When we de/serialize with setting TypeNameHandling = TypeNameHandling.All, the .NET type name will always be included when serializing. Without the type information, it's hard for converter to decide which class will be de/serialized.
2. Any performance or negative impacts that I should be aware of with this setting.
As remarked in Json.NET Documentation, TypeNameHandling should be used with caution when your application deserializes JSON from an external source and you should create a custom SerializationBinder when deserializing with a value other than TypeNameHandling.None.
You could reference the following links
http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_TypeNameHandling.htm
https://mallibone.com/post/serialize-object-inheritance-with-json.net
Is it somehow possible to use the XmlSerializer to deserialize its data into an existing instance of a class rather than into a new one?
This would be helpful in two cases:
Easily merge two XML files into one object instance.
Let object constructer itself be the one who is loading its data from the XML file.
If the is not possible by default it should work by using reflection (copying each property after the deserialisation) but this would be an ugly solution.
Basically, you can't. XmlSerializer is strictly constructive. The only interesting thing you can do to customize XmlSerializer is to implement IXmlSerializable and do everything yourself - not an attractive option (and it will still create new instances with the default constructor, etc).
Is xml a strict requirement? If you can use a different format, protobuf-net supports merging fragments into existing instances, as simply as:
Serializer.Merge(source, obj);
I think you're on the right track with the Reflection idea.
Since you probably have a wrapper around the XML operations anyway, you could take in the destination object, do the deserialization normally into a new object, then do something similar to cloning by copying over one by one only the properties holding non-default values.
It shouldn't be that complex to implement this, and it would look to consumers from the rest of your application just like in-place deserialization.
I hit the same problem a few weeks ago.
I put a method Deserialize(string serialized form) in the ISelfSerializable interface that an entity class of mine implemented. I also made sure the interface forced the class to have a default constructor.
In my factory I created an object of that type and then deserialized the string into it.
This is not thread safe thing to do... But you can do:
[Serializable]
public class c_Settings
{
static c_Settings Default;
public static SetExistingObject(c_Settings def)
{
Default = def;
}
public string Prop1;
public bool Prop2;
public c_Settings()
{
if (Default == null)
return;
MemberInfo[] members = FormatterServices.GetSerializableMembers(typeof(c_Settings));
FormatterServices.PopulateObjectMembers(this, members, FormatterServices.GetObjectData(Default, members));
}
}
This way you feed your object to deserialiser and deserialiser only overwrites whatever is written in .xml.