Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a better way to cast an JObject to any implementation of an interface? [duplicate]

Tags:

c#

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();
        }
    }
}
like image 409
Evaldas Raisutis Avatar asked Sep 01 '17 15:09

Evaldas Raisutis


2 Answers

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);
}
like image 115
Jamiec Avatar answered Nov 07 '22 00:11

Jamiec


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

like image 24
Evaldas Raisutis Avatar answered Nov 07 '22 00:11

Evaldas Raisutis