1

I have a JSON string I want to convert into one of many possible types depending on type:

{
"type": "x",
"val": {...}
}

type is an enum. val should only match a type associated with that particular enum key. All possible valid val's will have corresponding C# classes.

Is it possible to do this in one pass (instead of first parsing type only, and then using a switch to try and parse val into the correct type).

How would this be done recursively without having to hand write code to map the valid type/val combinations? E.g. if this "JSON enum" pattern was nested so the val object contains another type.

Thanks

zino
  • 1,222
  • 2
  • 17
  • 47
  • Please show examples of at least two types and how you would manually parse them. The more interesting question is what you're going to do with the deserialized value, as C# can't determine the type of `val` at compile time, so you'll have to "dot into" it, like `deserialized.val.SomePropOfEnum1.SomePropOfEnum2`. – CodeCaster Aug 13 '20 at 22:03
  • @CodeCaster I should have classes representing all the possible `val`'s. This is not dynamic JSON I know the structure. Im asking if it's possible to not have to map the `type` to the correct `val` manually - can it be done with attributes or generics? – zino Aug 13 '20 at 22:09
  • Have a look at [Deserializing polymorphic json classes without type information using json.net](https://stackoverflow.com/q/19307752/10263); it sounds similar to what you are asking. – Brian Rogers Aug 13 '20 at 22:17
  • You have classes, but not properties nor types. Given the JSON you show, how would you deserialize it? You can't use generics (`var o = JsonConvert.DeserializeObject>`), because you won't know you'll want a `Foo` before reading the JSON. Especially if it's recursive. So you could perhaps create a custom converter. But again, please show an example how you would do it with `switch()`, and show some actual input and usage. – CodeCaster Aug 13 '20 at 22:18
  • I dont have an example because I am currently working out the best method. Im thinking of using `var t = JsonConvert.DeserializeObject(jsonStr)` to get `type`, and then `switch(t.type){"x": {parseVal(jsonStr)}}`. Im looking for something like Rusts enum which can express an enum variant that contains a specific type https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html – zino Aug 13 '20 at 22:26

1 Answers1

0

Have you tried this one? I use Newtonsoft.Json and it works. I use "X" and "Y" strings and it parse them by custom converter.

Use Polymorphism to map the dynamic values.

Check the sample code:

public enum MyType
{
    X,
    Y
}

public class MyJsonWithType
{
    public MyType Type { get; set; }
    public IValueHeader Val { get; set; }
}

public interface IValueHeader
{

}

public class MyJsonDetail1 : IValueHeader
{
    public string SomeStr { get; set; }
}

public class MyJsonDetail2 : IValueHeader
{
    public int SomeInt { get; set; }
}

public class MyTypeConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(MyJsonWithType).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.StartObject)
        {
            JObject item = JObject.Load(reader);
            if (item["Type"] != null)
            {
                var myType = item["Type"].ToObject<MyType>(serializer);
                if (myType == MyType.X)
                {
                    var xObject = item["Val"].ToObject<MyJsonDetail1>(serializer);
                    return new MyJsonWithType
                    {
                        Type = myType,
                        Val = xObject
                    };
                }
                else if (myType == MyType.Y)
                {
                    var yObject = item["Val"].ToObject<MyJsonDetail2>(serializer);
                    return new MyJsonWithType
                    {
                        Type = myType,
                        Val = yObject
                    };
                }
            }
        }

        return null;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

static void Main(string[] args)
{
    string newJson1 = "{\"Type\":\"X\",\"Val\":{\"SomeStr\":\"Test\"}}";
MyJsonWithType newTypeX = JsonConvert.DeserializeObject<MyJsonWithType>(newJson1, new MyTypeConverter());

    string newJson2 = "{\"Type\":\"Y\",\"Val\":{\"SomeInt\":5566}}";
MyJsonWithType newTypeY = JsonConvert.DeserializeObject<MyJsonWithType>(newJson2, new MyTypeConverter());


    DisplayMyTypeValue(newTypeX.Val);
    DisplayMyTypeValue(newTypeY.Val);
}

private static void DisplayMyTypeValue(IValueHeader val)
{
    if (val is MyJsonDetail1)
    {
        Console.WriteLine((val as MyJsonDetail1).SomeStr);
    }
    else if (val is MyJsonDetail2)
    {
        Console.WriteLine((val as MyJsonDetail2).SomeInt);
    }
}
Li-Jyu Gao
  • 932
  • 6
  • 9
  • I don't think this is correct answer. Because the `Val` is always `MyJsonDetail` but question is that its type should be changed by the *Type* value. –  Aug 14 '20 at 04:20
  • @donggas90 Thank you for the advise, I edited the answer. – Li-Jyu Gao Aug 14 '20 at 05:38