简体   繁体   中英

Best Way to Retrieve Actual Concrete Object Of a Certain Type From a Json-String in C#?

I'm currently struggling with determining which types a json-string deserializer gives back in C#. I've tried to make a base TypeWHATEVER class where TypeX and TypeY inherited from them, but still whenever I try to retrieve the actual object with that specific type, it doesn't work since both can create a new instance with the same constructor.

Let's say I have TypeX and TypeY. They both have similar constructors, for example: both have a constructor with parameters (string a, string b). However, TypeY has an additional constructor with parameters, for example (string a, string b, string c).

Now whenever I use the additional constructor or even just the similar constructor in TypeY, it still doesn't produce the results I want (It still sees it as TypeX as well as TypeY)... In one of the methods I don't know which type to return, because the object is being deserialized from a Json-String. Also putting T and on the method that gives the object backs works, but it works for both classes which isn't what I want... I even put a enum in each different underlying Type classes, but that didn't work since the Type is defined in the base class as well.

How or what is the best way to determine how to get the actual object type from a json string where there are similar constructors, but still they differ from each other...

Please help me through this:(

Below you'll find sample code of what I'm trying to achieve..

Like I said earlier I have a base class or interface doesn't matter and two child classes

Base Class: Eg Parent.cs

public class Parent
{
    public virtual ClassType Type { get; protected set; } = ClassType.Undefined;//this is an enum and used to differentiate between the different classes. However, this doesn't work, because of the parent type will always be Undefined whenever a child type is converted from json-string to explicit Parent type...
    public string Id { get; set; }
    public string Message { get; set; }

    public Parent()
    {

    }

    public Parent(string id, string message = "")
    {
        Id = id;
        Message = message;
    }
}

Child Class: Eg ChildA.cs

public class ChildA : Parent
{
    public override ClassType Type => ClassType.Default;

    public ChildA()
    {

    }

    public ChildA(string id, string message = "") : base(id, message)
    {

    }
}

Child Class: Eg ChildB.cs

    public class ChildB : Parent
{
    private object testObjectA;
    private object testObjectB;
    private object testObjectC;

    public override ClassType Type => ClassType.New;

    public object TestObjectA
    {
        get { return testObjectA; }
        set { testObjectA = value; }
    }

    public object TestObjectB
    {
        get { return testObjectB; }
        set { testObjectB = value; }
    }

    public object TestObjectC
    {
        get { return testObjectC; }
        set { testObjectC = value; }
    }

    public ChildB()
    {

    }

    public ChildB(string id, string message) : base(id, message)
    {

    }

    public ChildB(string id, IDictionary<string, object> collection, string message = "") : this(id, message) // should I use 'this' or 'base' here for id and message, because I already get the base class for the second constructor and it makes sense to me just to take this class since the other is already been using the base class?
    {
        testObjectA = collection[Constants.A];
        testObjectB = collection[Constants.B];
        testObjectC = collection[Constants.C];
    }
}

Json Converter Class: Eg JsonConverterClass.cs

public static T StringToObject<T>(string json)
        => JsonConvert.DeserializeObject<T>(json);//using Newtonsoft.Json;

Now I have a Json-String that I want to convert to either a ChildA or ChildB. The Json-String is something like this:

{
  "type": "New",//This is the ClassType for example
  "id": "Some String...",
  "message": "",
  "collection": {
   "objectA": "A",
   "objectB": "B",
   "objectC": "C",
  }
}

Let's try to convert the Json-string which won't work and give that Child object back in a method, unfortunate this doesn't work:

public Parent GetObjectExample(string json_String)
    {
        Parent parentClass = JsonConverterClass.StringToObject<Parent>(json_String);// I don't know how I maybe can use reflection here to accomplish, maybe this is an option too?

        switch (parentClass.Type)
        {
            case ClassType.Default:
                parentClass = parentClass as ChildA;
                break;
            case ClassType.New:
                parentClass = parentClass as ChildB;
                break;
        }

        return parentClass;
    }

The problem here is that I expect ChildB to given back. However, since both classes have the same constructor. It doesn't recognizes to give ChildB back. In fact, it just gives a random class back.. Either ChildA or ChilB and in most cases it just gives ChildA back what's really weird.

Hopefully, I could be clear as possible to inform you about what is going on and I really don't know why my approach doesn't work...

Ok, I think there are many things to comment. Let's begin:

In your constructor with 3 parameters, use this instead of base . Imagine this situation:

public ChildB(string id, string message) 
    : base(id, message)
{
    this.FullName = $"{id} {message}";
}

public ChildB(string id, IDictionary<string, object> collection, string message = "") 
    : this(id, message)
{
}

Calling this , your 3 params constructor run the 2 params constructor setting the FullName property. If you use base , FullName won't be initialized and you must duplicate this line in your 3 params constructor.

C# know the type of any object. Really, you don't need your ClassType property. You can do things like:

if (myChildBInstance.GetType() == typeof(ChildB))

I recomend you remove this property.

Also, that property is protected. You can't change it's value from serialization. Serialization (by default) works with public properties.

The key in your question, I think, is with the constructors. When you serialize, you use always the parameterless constructor. In the deserialization, constructor without parameters is invoked and later, the properties are setted.

Your Type property in the JSON string is never used. You can't set the value because is protected and your instance is created before you start processing these properties.

var json = @"{
      'type': 'New',//This is the ClassType for example
      'id': 'Some String...',
      'message': '',
      'collection': {
       'objectA': 'A',
       'objectB': 'B',
       'objectC': 'C',
      }
    }";
var obj = StringToObject<ChildB>(json);

When you run the previous code, a ChildB object is created (because T type of StringToObject ) and then, all the JSON string (public) properties are setted. So it's impossible that your type property may be useful to choose the type of the object. collection is not a property of your object: this is the reason why your object don't get these A,B,C values.

With this sample:

var childB = new ChildB
{
    Id = "Id",
    Message = "Msg",
    TestObjectA = 1,
    TestObjectB = 2,
    TestObjectC = 3
};
var json = JsonConvert.SerializeObject(childB);

Your JSON must be like this:

{
   "TestObjectA":1,
   "TestObjectB":2,
   "TestObjectC":3,
   "Id":"Id",
   "Message":"Msg"
}

And then, you get your valid object in this way:

var obj = StringToObject<ChildB>(json);

If you are going to work with distinct types and inheritance, then you must use the TypeNameHandling = TypeNameHandling.All that I commented before i my link https://stackoverflow.com/a/72270480/18452174 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM