0

I have a simple API which result in different types of config for different tasks, So I am looking to find a way to parse the response dynamically using if/else. But for some reason, the config is not deserialized because that's a string already and Serialize() do it again.

So, the JsonConvert.DeserializeObject<typeAhandler>(jsonString) doesn't work then.

Here is my code :

void Main()
{
    var json = new ApiResponse {name = "name1", type = "a", config = "{'title' : 'hello world!', 'id' : 1}"};
    var jsonString = JsonConvert.SerializeObject(json);
    Console.WriteLine(jsonString);

    if (json.type == "a")
    {
        var handler = JsonConvert.DeserializeObject<typeAhandler>(jsonString);
        handler.Dump();
    }
    else if(json.type == "b")
    {
        var handler = JsonConvert.DeserializeObject<typeBhandler>(jsonString);
        handler.Dump();
    }
}

public class ApiResponse
{
    public string name { get; set; }
    public string type {get; set;}
    public string config {get;set;} 
}

// Type A Handler
public class typeAhandler
{
    public string name { get; set; }
    public typeAconfig config { get; set; }
}

public class typeAconfig
{
    public string title { get; set; }
    public int id { get; set; }
}

// Type B Handler
public class typeBhandler
{
    public string name { get; set; }
    public typeBconfig config { get; set; }
}

public class typeBconfig
{
    public string[] items { get; set; }
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Vikash Rathee
  • 1,776
  • 2
  • 25
  • 43
  • 1
    Assuming you make `typeAhandler` and `typeBhandler` inherit from some common abstract base class or interface, this looks to be a dupe of [Json.Net Serialization of Type with Polymorphic Child Object](https://stackoverflow.com/q/29528648/3744182), [Deserializing polymorphic json classes without type information using json.net](https://stackoverflow.com/q/19307752/3744182)... – dbc Nov 28 '20 at 15:30
  • 1
    ... [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). Do you want another answer that basically duplicates all those? – dbc Nov 28 '20 at 15:34
  • 1
    Actually, what is your problem? In your question you are **serializing** `ApiResponse` to JSON and then trying to **deserialize** that same JSON to `typeAhandler` or `typeBhandler` - which doesn't really make sense. Is your real question that you are trying to **deserialize** some JSON to `ApiResponse` and sense the correct type for `config` as you are doing so? And, do you have control over the JSON format, or is that fixed? – dbc Nov 28 '20 at 16:17

5 Answers5

1
class Program
{
    static void Main(string[] args)
    {
        var json = new ApiResponse { Name = "name1", Type = "a", Config = "{'title' : 'hello world!', 'id' : 1}" };
        //var json = new ApiResponse { Name = "name1", Type = "b", Config = "{'items' : ['item1', 'item2']}" };
        string apiResult = JsonConvert.SerializeObject(json);

        var deserialized = JsonConvert.DeserializeObject<ApiResponse>(apiResult);
        if (deserialized.Type == "a")
        {
            // You deserialized 'Name' before and already you have the value in 'deserialized' variable
            var result = new TypeAHandler() { Name = deserialized.Name };
            // You need just 'Deserialize' the 'Config' property
            result.Config = JsonConvert.DeserializeObject<TypeAConfig>(deserialized.Config);
            Console.WriteLine("Name: {0}", result.Name);
            Console.WriteLine("Id: {0}", result.Config.Id);
            Console.WriteLine("Title: {0}", result.Config.Title);
        }
        else if (deserialized.Type == "b")
        {
            var result = new TypeBHandler() { Name = deserialized.Name };
            result.Config = JsonConvert.DeserializeObject<TypeBConfig>(deserialized.Config);
            Console.WriteLine("Name: {0}", result.Name);
            Console.WriteLine("Items: ");
            foreach (var item in result.Config.Items)
            {
                Console.WriteLine(item.ToString());
            }
        }

        Console.ReadKey();
    }
}
public class ApiResponse
{
    public string Name { get; set; }
    public string Type { get; set; }
    public string Config { get; set; }
}

public class TypeAHandler
{
    public string Name { get; set; }
    public TypeAConfig Config { get; set; }
}


public class TypeAConfig
{
    public int Id { get; set; }
    public string Title { get; set; }
}

public class TypeBHandler
{
    public string Name { get; set; }
    public TypeBConfig Config { get; set; }
}

public class TypeBConfig
{
    public string[] Items { get; set; }
}

Because your Config property is not a nested object, I deserialized it separately.

This is the result when type:a

type a

This is the result when type:b

type b

Saeid Amini
  • 1,313
  • 5
  • 16
  • 26
1

I would do it like this:

static void Main()
{
    var json = new ApiResponse {name = "name1", type = "a", config = "{'title' : 'hello world!', 'id' : 1}"};
    var jsonString = JsonConvert.SerializeObject(json);
    Console.WriteLine(jsonString);

    if (json.type == "a")
    {
        var handler = JsonConvert.DeserializeObject<typeAhandler>(jsonString);
        // handler.Dump();
    }
    else if(json.type == "b")
    {
        var handler = JsonConvert.DeserializeObject<typeBhandler>(jsonString);
        // handler.Dump();
    }
}

public class ApiResponse
{
    public string name { get; set; }
    public string type { get; set; }
    public string config { get; set; }
}

// Type A Handler
public class typeAhandler
{
    [JsonProperty("name")]
    public string name { get; set; }
    [JsonIgnore] public typeAconfig config { get; set; }

    [JsonProperty("config")]
    public dynamic DynamicConfig
    {
        set => config = JsonConvert.DeserializeObject<typeAconfig>(value);
    }

}

public class typeAconfig
{
    public string title { get; set; }
    public int id { get; set; }
}

// Type B Handler
public class typeBhandler
{
    [JsonProperty("name")]
    public string name { get; set; }
    
    [JsonIgnore]
    public typeBconfig config { get; set; }
    
    [JsonProperty("config")]
    public dynamic DynamicConfig
    {
        set => config = JsonConvert.DeserializeObject<typeBconfig>(value);
    }
}

public class typeBconfig
{
    public string[] items { get; set; }
}

The key is to expose the config type as dynamic to the JsonComnvert and then behind scene convert it to your desired type and use it in your app

Ali Alp
  • 691
  • 7
  • 10
0

Consider doing it as below -

Maintain a dictionary of handler to avoid if...else.

 public static void Main(string[] args)
 {
        var json = new ApiResponse { name = "name1", type = "a", config = "{'title' : 'hello world!', 'id' : 1}" };
        var jsonString = JsonConvert.SerializeObject(json);
        Console.WriteLine(jsonString);
        IDictionary<string, Type> handlers = new Dictionary<string, Type>
        {
            { "a", typeof(typeAconfig) },
            { "b", typeof(typeBconfig) }
        };
        var handler = DeserializeHandler(jsonString, handlers);
 }

The function DeserializeHandler is as below -

   public static object DeserializeHandler(string jsonString, IDictionary<string, Type> handlers)
    {
        var jsonObject = JsonConvert.DeserializeObject<JToken>(jsonString);
        var type = jsonObject["type"].ToString();
        var handlerType = handlers[type];
        var config = jsonObject["config"].ToString();
        var handler = JsonConvert.DeserializeObject(config, handlerType);
        return handler;
    }
Nikhil Patil
  • 2,480
  • 1
  • 7
  • 20
0

I am not 100% clear on what you want but you could do something like this using custom attributes to avoid the if/else and reflection to do your object construction.

internal class Program
{
    public static void Main(string[] args)
    {
        var json = new ApiResponse { Name = "name1", Type = "a", Config = "{'title' : 'hello world!', 'id' : 1}" };
        var jsonString = JsonConvert.SerializeObject(json);
        ParseAndDumpConfig(jsonString);

        json = new ApiResponse { Name = "name2", Type = "b", Config = "{'Items' : ['Item-1', 'Item-2', 'Item-3']}" };
        jsonString = JsonConvert.SerializeObject(json);
        ParseAndDumpConfig(jsonString);
    }

    private static void ParseAndDumpConfig(string jsonString)
    {
        Console.WriteLine();
        Console.WriteLine("Parsing Api Response:");
        Console.WriteLine(jsonString);

        var json = JsonConvert.DeserializeObject<ApiResponse>(jsonString);

        if (_typeMap.TryGetValue(json.Type, out var t))
        {
            var config = JsonConvert.DeserializeObject(json.Config, t);

            Type genericType = typeof(TypeHandler<>).MakeGenericType(t);

            var constructedObject = Activator.CreateInstance(genericType, new object[] { json.Name, config });

            var result = Convert.ChangeType(constructedObject, genericType);

            Console.WriteLine($"Parsed to [{result.GetType().FullName}]:");
            Console.WriteLine(result);
        }
        else
        {
            Console.WriteLine($"Config map for \"{json.Type}\" not found.");
        }
    }

    private static readonly Dictionary<string, Type> _typeMap = Assembly.GetExecutingAssembly().GetTypes()
        .Where(x => x.CustomAttributes.Any(y => y.AttributeType == typeof(DeserializeTypeMapAttribute)))
        .ToDictionary(x => x.GetCustomAttribute<DeserializeTypeMapAttribute>().Name);
}

public class ApiResponse
{
    public string Name { get; set; }
    public string Type { get; set; }
    public string Config { get; set; }
}

public class TypeHandler<TConfig>
{
    public string Name { get; set; }
    public TConfig Config { get; set; }

    public TypeHandler(string name, TConfig config)
    {
        Name = name;
        Config = config;
    }

    public override string ToString()
    {
        return JsonConvert.SerializeObject(this);
    }
}

[DeserializeTypeMap("a")]
public class TypeAconfig
{
    public string Title { get; set; }
    public int Id { get; set; }

    public override string ToString()
    {
        return JsonConvert.SerializeObject(this);
    }
}

[DeserializeTypeMap("b")]
public class TypeBconfig
{
    public string[] Items { get; set; }

    public override string ToString()
    {
        return JsonConvert.SerializeObject(this);
    }
}

[AttributeUsage(AttributeTargets.Class)]
public class DeserializeTypeMapAttribute : Attribute
{
    public string Name { get; }

    public DeserializeTypeMapAttribute(string name)
    {
        Name = name;
    }
}
mooku
  • 31
  • 3
-1

4 times in 3 days I've posted the same answer for different questions :-)

Use a specific JsonConvert class: C# Convert Json to object with duplicated property name with different data type

Neil
  • 11,059
  • 3
  • 31
  • 56