6

I am not sure if there's a better way to do this. maybe someone help?

I want to cast an object of type JObject to a class in a factory. Class itself should be decided based on on another parameter. But I can only think of Serializing the object to a string an serializing back into a specific class. There has to be a better way?

https://dotnetfiddle.net/3Qwq6V

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;

namespace Test
{
    public class Input
    {
        public int TypeId { get; set; }
        public object ObjectDefinesInput;
    }

    public class VoiceInput
    {
        public string Language;
    }

    public class TextInput
    {
        public string Encoding;
    }

    public interface IResponse
    {
        void Respond();
    }

    public class VoiceResponse : IResponse
    {
        private VoiceInput input { get; set; }
        public VoiceResponse(VoiceInput input) { this.input = input; }

        public void Respond()
        {
            // use information on VoiceInput to do something
            Console.WriteLine("(In "+ this.input.Language +"): beep buup boop.");
        }
    }

    public class TextResponse : IResponse
    {
        private TextInput input { get; set; }
        public TextResponse(TextInput input) { this.input = input; }
        public void Respond()
        {
            Console.WriteLine("I am a text handler. Using "+ this.input.Encoding +".");
        }
    }

    public static class ResponseFactory
    {
        public static IResponse CreateResponseHandler(Input input)
        {
            // ----------------- ISSUE HERE -----------------------------//
            // I'm using JsonConvert to serialize an <object> to a string, and then 

            string jsonObjectDefinesInput = JsonConvert.SerializeObject(input.ObjectDefinesInput, new JsonSerializerSettings
            {
                Formatting = Formatting.Indented,
                ContractResolver = new CamelCasePropertyNamesContractResolver()
            });

            switch (input.TypeId)
            {
                case 1:
                    // (VoiceInput) input.ObjectDefinesInput throws exception
                    // input.ObjectDefinesInput as VoiceInput returns null
                    VoiceInput voiceInput = JsonConvert.DeserializeObject<VoiceInput>(jsonObjectDefinesInput);
                    return new VoiceResponse(voiceInput);
                default:
                    TextInput textInput = JsonConvert.DeserializeObject<TextInput>(jsonObjectDefinesInput);
                    return new TextResponse(textInput);
            }
        }
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            string jsonData1 = "{ \"typeId\": 1, \"ObjectDefinesInput\": { \"Language\": \"Robot\" } }";
            string jsonData2 = "{ \"typeId\": 2, \"ObjectDefinesInput\": { \"Encoding\": \"UTF-8\" } }";

            Input someInpput1 = JsonConvert.DeserializeObject<Input>(jsonData1);
            Input someInpput2 = JsonConvert.DeserializeObject<Input>(jsonData2);

            IResponse testResponse1 = ResponseFactory.CreateResponseHandler(someInpput1);
            IResponse testResponse2 = ResponseFactory.CreateResponseHandler(someInpput2);

            testResponse1.Respond();

            testResponse2.Respond();

            Console.ReadLine();
        }
    }
}
Evaldas Raisutis
  • 1,628
  • 4
  • 18
  • 32
  • 2
    Pls provide the code inside this question, so if someone looks at your question in 3 years also sees the code belonging to it. External resources may get deleted by times. – Tobias Theel Sep 01 '17 at 15:37
  • When you say "object of type object", should we assume you mean "reference of type object to something definitely *not* of type object"? Because if the actual runtime type of the object is `object`, there's little future in casting it to anything else. I glanced at your fiddle but didn't see any indication of where the relevant code is. – 15ee8f99-57ff-4f92-890c-b56153 Sep 01 '17 at 15:38
  • @EdPlunkett assume I have Deserialized Json into type object. There is no underlying class (other than maybe JObject returned by JsonConvert?) – Evaldas Raisutis Sep 01 '17 at 15:41
  • 1
    @TobiasTheel good point.Done. – Evaldas Raisutis Sep 01 '17 at 15:41
  • 1
    The runtime type of any object `o` can be determined by calling `o.GetType()`. – 15ee8f99-57ff-4f92-890c-b56153 Sep 01 '17 at 15:42
  • @EdPlunkett It's just {Name = "JToken" FullName = "Newtonsoft.Json.Linq.JToken"} – Evaldas Raisutis Sep 01 '17 at 15:44
  • 2
    Why are you serializing and deserializing `input` in `CreateResponseHandler`? What is the goal there? What do you get from `input.GetType()`? Are you saying that you just want `case 1: return new VoiceResponse((VoiceInput)input);`? – 15ee8f99-57ff-4f92-890c-b56153 Sep 01 '17 at 15:45
  • @EdPlunkett I want to return an implementation of the interface based on the TypeId on the input class and pass a parameter into the constructor of that implementation class. Without knowing how the input is structured when I call the factory method. This way any of the implementation classes can rely on receiving information required to run from the object – Evaldas Raisutis Sep 01 '17 at 15:50
  • @EdPlunkett (VoiceInput)input will throw null exception, but yes basically that is what I want. – Evaldas Raisutis Sep 01 '17 at 15:51
  • Whoops, I missed a bit of your code that's hidden by the horizontal scrolling. What I should have suggested was `case 1: return new VoiceResponse((VoiceInput)input.ObjectDefinesInput);` -- `input.ObjectDefinesInput` is what you're serializing and then deserializing. – 15ee8f99-57ff-4f92-890c-b56153 Sep 01 '17 at 15:53
  • @EdPlunkett yes I tried that, but it just throws null – Evaldas Raisutis Sep 01 '17 at 15:54
  • 1
    I'm guessing a bit though since I have only a tenuous inference about what `input.ObjectDefinesInput` is. – 15ee8f99-57ff-4f92-890c-b56153 Sep 01 '17 at 15:54
  • Does "throws null" mean "throws NullReferenceException"? What is `input.ObjectDefinesInput` before you serialize it? What is its value? Is it null? If not null, what does `input.ObjectDefinesInput.GetType()` return? – 15ee8f99-57ff-4f92-890c-b56153 Sep 01 '17 at 15:54
  • @EdPlunkett it's a JObject. – Evaldas Raisutis Sep 01 '17 at 15:56

2 Answers2

2

If you just supplied the right sort of input, you could cast them:

var voiceInput = new Input()
{
    TypeId = 1,
    ObjectDefinesInput = new VoiceInput(){ ... }
}

and

switch (input.TypeId)
{
    case 1:
          VoiceInput voiceInput = (VoiceInput)input.ObjectDefinesInput;
          return new VoiceResponse(voiceInput);
    default:
          TextInput textInput = (textInput)input.ObjectDefinesInput;
          return new TextResponse(textInput );
}

If you want some type safety, make your Input class have a generic type argument for the type of input

public class Input<T>
{
    public int TypeId { get; set; }
    public T ObjectDefinesInput;
}

And

var voiceInput = new Input<VoiceInput>()
{
    TypeId = 1,
    ObjectDefinesInput = new VoiceInput(){ ... }
}

Then no casting required:

switch (input.TypeId)
{
    case 1:
          return new VoiceResponse(input.ObjectDefinesInput);
    default:
          return new TextResponse(input.ObjectDefinesInput);
}
Jamiec
  • 133,658
  • 13
  • 134
  • 193
1

Hmm, since I found out the type is actually JObject (as I deserialize into < object >, the underlying class is JObject), then I can do

input.ObjectDefinesInput.ToObject<TextInput>();

as per Converting a JToken (or string) to a given Type

Evaldas Raisutis
  • 1,628
  • 4
  • 18
  • 32
  • 1
    I finally pasted your code into VS and got it through my head what the real issue is. This is a nice feature I hadn't known about. +1. May I suggest retitling your question to something like "How to cast JObject to actual type that was serialized"? – 15ee8f99-57ff-4f92-890c-b56153 Sep 01 '17 at 16:07