2

This is basically a follow-on to the question Newtonsoft Object → Get JSON string .

I have an object that looks like this:

[JsonConverter(typeof(MessageConverter))]
public class Message
{
    public Message(string original)
    {
        this.Original = original;
    }

    public string Type { get; set; }

    public string Original { get; set; }
}

My requirement is to store the original JSON string as part of the object when I initialise it. I have been able to (partially) successfully achieve this using a custom JsonConverter and then basically doing something like this in the JsonConverter:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
        return null;

    JObject obj = JObject.Load(reader);
    return new Message(obj.ToString(Formatting.None))
    {
        Type = obj["type"].ToString()
    };
}

However the problem I run into is when I try to inherit from Message with something like

public class CustomMessage : Message
{
    public string Prop1 { get; set; }
}

For obvious reasons my code fails because it tries to return a new Message() not a new CustomMessage().

So short of implementing a big if statement with all my sub-types and manually binding using something like JObject["prop"].ToObject<T>() how can I successfully populate the Original property while still binding all my sub-type values using the default deserialisation?

NOTE: The reason this is being done is because the original message might contain data that isn't actually being bound so I can't just add a property which serialises the object as it stands.

Maxim Gershkovich
  • 45,951
  • 44
  • 147
  • 243
  • is this a typo?? **return new GDAXMessage** or is it **return new Message** ?? – Rudresha Parameshappa Mar 27 '18 at 15:39
  • Yeah sorry typo fixed it. – Maxim Gershkovich Mar 27 '18 at 15:40
  • Take a look at [How can I serialize and deserialize a type with a string member that contains “raw” JSON, without escaping the JSON in the process](https://stackoverflow.com/q/40529125/3744182). You should be able to apply `[JsonConverter(typeof(RawConverter))]` to `Original`. – dbc Mar 27 '18 at 15:41
  • @dbc That helps a bit because it makes sense that I should just add to the property a custom serialiser but the problem is that Original is not part of the JSON message and gets skipped. Basically I need the example you provided but with a runtime defaultvalue provider. – Maxim Gershkovich Mar 27 '18 at 16:05

2 Answers2

2

The following solution works

One thing you can do is to decorate each child class by generic JsonConverter attribute.

[JsonConverter(typeof(MessageConverter<Message>))]
public class Message
{
    public Message(string original)
    {
        this.Original = original;
    }

    public string Type { get; set; }

    public string Original { get; set; }
}
[JsonConverter(typeof(MessageConverter<CustomMessage>))]
public class CustomMessage : Message
{
    public CustomMessage(string original) : base(original)
    {
    }
    public string Prop1 { get; set; }
}

public class MessageConverter<T> : JsonConverter where T : Message
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
            return null;

        JObject obj = JObject.Load(reader);
        var customObject = JsonConvert.DeserializeObject<T>(obj.ToString(), new JsonSerializerSettings 
                        {
                            ContractResolver = new CustomContractResolver()
                        });
        customObject.Original = obj.ToString();
            return customObject;
    }

    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException();
    }
}

//This will remove our declared Converter
public class CustomContractResolver : DefaultContractResolver
{
    protected override JsonConverter ResolveContractConverter (Type objectType)
    {
        return null;
    }
}

And then you can use the same serializer for all of the child classes

CustomMessage x = JsonConvert.DeserializeObject<CustomMessage>("{\"type\":\"Test\",\"Prop1\":\"Prop1\"}");
 Message y = JsonConvert.DeserializeObject<Message>("{\"type\":\"Test\"}");

Here is the output screen shot enter image description here

Rudresha Parameshappa
  • 3,826
  • 3
  • 25
  • 39
  • Although this is sorta on the right track. Thats not going to work because the remainder of the object properties will not be deserialised. Try changing your example code to "{\"type\":\"Test\", \"Prop1\":\"Test2\"}" and you'll see the issue. – Maxim Gershkovich Mar 27 '18 at 16:03
  • @MaximGershkovich I think I found the solution :-) Please check the updated answer – Rudresha Parameshappa Mar 27 '18 at 17:49
  • Yeah, thanks for the suggestion and while I've upvoted your solution because it technically solves the problem. I'm not really sure that I like your solution better than mine. The answer is probably that its not worth the effort. I'm going to leave the question open for a bit longer to see if anyone else comes up with a better solution. – Maxim Gershkovich Apr 03 '18 at 14:40
1

I'm leaving the question open in the hope that someone comes up with a better answer but I've temporarily used the following solution to resolve my problem.

public static class MessageExtensions
{
    public static T Deserialize<T>(this string message) where T : Message
    {
        T instance = Activator.CreateInstance<T>();
        instance.Original = message;
        JsonConvert.PopulateObject(message, instance);
        return instance;
    }
}
Maxim Gershkovich
  • 45,951
  • 44
  • 147
  • 243