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.

4
  • is this a typo?? return new GDAXMessage or is it return new Message ?? Commented Mar 27, 2018 at 15:39
  • Yeah sorry typo fixed it. Commented Mar 27, 2018 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. You should be able to apply [JsonConverter(typeof(RawConverter))] to Original. Commented Mar 27, 2018 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. Commented Mar 27, 2018 at 16:05

2 Answers 2

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

Sign up to request clarification or add additional context in comments.

3 Comments

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.
@MaximGershkovich I think I found the solution :-) Please check the updated answer
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.
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;
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.