-1

I don't have power to impact the incoming JSON schema so i need find solution.

Say i have some simple DTO classes:

public class ClassA { prop1, prop2, ... }
public class ClassB { prop3, prop4, ... }

And the incoming JSON is like:

{
  "type": "determinant",
  "data": { ... }
}

Of course i created root DTO class:

public class CallbackEvent
{
    public string Type { get; set; }

    [JsonConverter(typeof(DataConverter))]
    public object Data { get; set; }
}

And in my DataConverter i haven't found any ways to access the root context of deserialization process. I do hope I was not searching very well

Warning: the unreal code snippet below

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    // i'd like to code smth like this
    var context = reader.Root;
    var typeValue = context["value"].GetValue<string>();

    switch (typeValue)
    {
        case "event_a": return serializer.Deserialize<ClassA>(reader);
        case "event_b": return serializer.Deserialize<ClassB>(reader);
        default: return null;
    }
}

I would really appreciate your help, folks!

skorenb
  • 629
  • 1
  • 9
  • 16
  • There's nothing like this in Json.NET. In the [JSON standard](https://json.org/) an object is defined as an *unordered set of name/value pairs* so there's no guarantee the `"type"` property has even been read when processing the `"data"` property. You could preload into a `JToken` hierarchy, or add the conversion logic to the parent as shown in e.g. [Json.Net Serialization of Type with Polymorphic Child Object](https://stackoverflow.com/q/29528648/3744182). – dbc Apr 18 '19 at 20:10
  • Make your converter handle the `CallbackEvent` class instead of the `Data` property. Then you can read the data into a `JObject` and get the type property from there to decide how to handle `Data`. Order of properties will not matter then. See [Deserializing polymorphic json classes without type information using json.net](https://stackoverflow.com/a/19308474/10263) for an example. – Brian Rogers Apr 18 '19 at 22:59

2 Answers2

0

You can do the conditional deserialization by creating a Json converter but on CallbackEvent instead on your Data property. It's a little bit more complex as you need to move on the json reader manually.

The drawback is that json properties of root object must be in the exact same order. And you need to handle manually any extra other properties of CallbackEvent class

void Main()
{
    var json1 = @"{
  ""type"": ""event_a"",
  ""data"": { }
}
";
    var json2 = @"{
  ""type"": ""event_b"",
  ""data"": { }
}
";

    var obj1 = JsonConvert.DeserializeObject<CallbackEvent>(json1, new DataConverter());
    var obj2 = JsonConvert.DeserializeObject<CallbackEvent>(json2, new DataConverter());
}

// Define other methods and classes here
public class ClassA
{
    public int Test { get; set; } = 2;
}
public class ClassB
{
    public int Type { get; set; } = 1;
}

public class CallbackEvent
{
    public string Type { get; set; }

    //[JsonConverter(typeof(DataConverter))]
    public object Data { get; set; }
}

public class DataConverter : JsonConverter<CallbackEvent>
{
    public override CallbackEvent ReadJson(JsonReader reader, Type objectType, CallbackEvent existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        reader.Read();

        if (reader.TokenType != JsonToken.PropertyName || (string)reader.Value != "type")
        {
            throw new InvalidOperationException();
        }

        reader.Read();

        if (reader.TokenType != JsonToken.String)
        {
            throw new InvalidOperationException();
        }

        string typeValue = reader.Value?.ToString();

        reader.Read();

        if (reader.TokenType != JsonToken.PropertyName || (string)reader.Value != "data")
        {
            throw new InvalidOperationException();
        }

        reader.Read();

        object data = null;

        switch (typeValue)
        {
            case "event_a": data = serializer.Deserialize<ClassA>(reader); break;
            case "event_b": data = serializer.Deserialize<ClassB>(reader); break;
            default : data = serializer.Deserialize<object>(reader); break;
        }

        reader.Read();

        return new CallbackEvent()
        {
            Data = data,
            Type = typeValue,
        };
    }

    public override void WriteJson(JsonWriter writer, CallbackEvent value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
Kalten
  • 4,092
  • 23
  • 32
-3

you can try use Object, or Dynamic

IRusio
  • 1
  • Perhaps you could expand your answer to explain a little more about your idea? Maybe add some example code? "Try to use object or dynamic" isn't very helpful on its own. – Brian Rogers Apr 18 '19 at 22:53