0

Let's say I have this little json snippet:

{
  "Type": "Bar",
  "BarOnly": "This is a string readable when deserialized to the Bar class only, as declared in my type key"
}

I also have these three classes:

public class Base
{
    public enum SampleEnum
    {
        Bar,
        Baz,
    }

    public SampleEnum Type
    {
        get;
        set;
    }
}

public class Bar : Base
{
    public string BarOnly
    {
        get;
        set;
    }
}

public class Baz : Base
{
    public string BazOnly
    {
        get;
        set;
    }
}

Based on the Type property in the json snippet, I'd like to have it deserialize to either Bar or Baz.
My first idea was to first deserialize it to the Base class, and then use its type and a switch statement to deserialize the JSON again to its respective class. (Using Newtonsoft.Json)

var type = JsonConvert.DeserializeObject<Base>(json).Type;
string message = "";
switch (type)
{
    case (Base.SampleEnum.Bar):
        message = JsonConvert.DeserializeObject<Bar>(json).BarOnly;
        break;
    case (Base.SampleEnum.Baz):
        message = JsonConvert.DeserializeObject<Baz>(json).BazOnly;
        break;
}
Console.WriteLine(message);

Needless to say that this process is extremely redundant, tedious and, since the switch statement is hard-coded, not very "dynamic" at all.
Another idea was to use a generic class as the base class instead and passing in the type of the actual class it should deserialize to, but then I end up with the same switch statement to figure out what that class should be.
Since you can't map enums to class types, I also thought about using a Dictionary to map the possible enum values to their class counterparts; this still makes the mapping process hard-coded though.
Is there any way I can dynamically get the corresponding class to deserialize to based on the type property of the json object?

EDIT: There seems to be some confusion about how this is supposed to be used and how the data is fetched; let me provide some background information.
I'm iterating through a directory with a lot of different spreadsheet files, mostly CSVs and XML files. Each of these feeds have a "meta file", describing how to process their content. This includes checksums, delimiters and other information. They also declare of what type their parent file is (CSV, XML etc). Hence, they share a lot of common properties (like the Base class in my example), but also have their own set of properties. They derive from an abstract class that requires them to implement a function that returns an instance of the corresponding feed processing class, initialized with values directly from within the meta class. I hope this makes sense.

3x071c
  • 955
  • 10
  • 40
  • Use reflection to find the corresponding class, then use reflection to call a method to deserialize. – Lasse V. Karlsen Apr 19 '20 at 18:06
  • 1
    Normally, Newtonsoft Json can put $type information into the json content. Is this `Type` property yours? – Oguz Ozgul Apr 19 '20 at 18:08
  • @LasseV.Karlsen Sure, reflection is always an answer ;) I'd rather not like to get my hands dirty with it if there is some other way around it, though. – 3x071c Apr 19 '20 at 18:10
  • Why don't you put an abstract method into the Base class and let extending classes implement it, like public abstract string GetMessage(); – Oguz Ozgul Apr 19 '20 at 18:10
  • So, Bar can return BarOnly and Baz can return BazOnly, without you knowing anything about the concrete classes? No enums, no type checking, something "dynamic" I guess :) – Oguz Ozgul Apr 19 '20 at 18:11
  • Check this fiddle - https://dotnetfiddle.net/VZ1QXi - note that it doesn't deal with namespaces because the fiddle doesn't use one, so if you add namespaces into the mix it will need some changes. – Lasse V. Karlsen Apr 19 '20 at 18:12
  • @OguzOzgul Making the class abstract is my plan, however I tried to reduce the example in my question above down to the bare minimum. I have a second set of classes that correspond to each of the deserialization classes and process the data, hence there is no "unknown" factor involved – 3x071c Apr 19 '20 at 18:14
  • You can mark the method virtual though. – Oguz Ozgul Apr 19 '20 at 18:14
  • Is this some information returned from a Ajax request? – Mosia Thabo Apr 19 '20 at 18:16
  • Oh sorry. You are at the deserialization stage yet. Are you putting the Type property inside the json? If yes, you can have the serializer put it as $type and the deserialization to the correct concrete type will be automatic. See: https://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm – Oguz Ozgul Apr 19 '20 at 18:24
  • You can create a custom [`JsonConverter`](https://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm) to deserialize your class hierarchy. Please see [Deserializing polymorphic json classes without type information using json.net](https://stackoverflow.com/q/19307752/3744182), [Json.Net Serialization of Type with Polymorphic Child Object](https://stackoverflow.com/q/29528648/3744182) or [How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?](https://stackoverflow.com/q/8030538/3744182) for details. – dbc Apr 19 '20 at 18:36
  • @LasseV.Karlsen - `JsonConvert.DeserializeObject(json, actualType)` seems like it could be open to [Friday the 13th: JSON Attacks](https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf) like the ones described in [TypeNameHandling caution in Newtonsoft Json](https://stackoverflow.com/q/39565954/3744182). – dbc Apr 19 '20 at 18:40
  • Yes, probably is, it would be better to have a list of approved type names and the types they map to. – Lasse V. Karlsen Apr 19 '20 at 19:16

1 Answers1

1

@OguzOzgul commenting is correct. I've done this countless of times for objects that are composed with interfaces that need to be serialized and deserialized.

See TypeNameHandling for Newtonsoft: https://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm

Your json file will look ever so slightly different:

{
    "$type": "SomeNamespace.Bar",
    "BarOnly": "This is a string readable when deserialized to the Bar class only, as declared in my type key"
}

If you use

new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All
}

During serialization, it will add the full type name of all objects to make sure newtonsoft knows what their type is during deserialization (given you use the same settings then). That way you do not have to write your own custom code for type detection.

  • 2
    This is the right answer, but it's also worth pointing out that caution is needed with `TypeNameHandling.All`. It should not be used on public-facing APIs where the JSON input could be crafted by a human. See https://www.alphabot.com/security/blog/2017/net/How-to-configure-Json.NET-to-create-a-vulnerable-web-API.html – Nate Barbettini Apr 20 '20 at 17:44
  • Oooo that is good to know @NateBarbettini, thanks for the info :) in retrospect I've luckily only used it for private API's – Davey van Tilburg Apr 20 '20 at 17:46