2

Let's say I've defined an interface:

public interface IExtensibleObject : IDictionary<string, object>
{
    // Some members here, but it doesn't matter for this question
}

And I've designed a class which implements the whole interface:

public class Customer : IExtensibleObject
{
     public Guid Id { get; set; }

     // IExtensibleObject implemented members
}

When I try to deserialize a JSON string to Customer, JSON.NET will access the IDictionary<TKey, TValue> indexer to set Id property (i.e. instance["Id"] = value):

Customer customer = JsonConvert.DeserializeObject<Customer>(@"{""Id"":""bcf66a92-00ea-4124-afa7-a6c200ae5886""}");

Is there some built-in way of avoiding the whole behavior?. I need to deserialize the whole object as a regular object even when it implements IDictionary<TKey, TValue>.

Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • I don't know the answer to your question. But perhaps you should reconsider using IDictionary (or any collection class/interface) as a base class with properties tacked on to it, for a lot of good reasons spelled out here: http://stackoverflow.com/a/21694054/1220550 – Peter B Nov 18 '16 at 11:30
  • @PeterB It's not the case. In my case, I did it because certain entities should allow custom properties during run-time... – Matías Fidemraizer Nov 18 '16 at 11:31
  • Okay, but you could also put those custom properties *in* a Dictionary type property of the Customer class. Normally the properties of a dictionary are all related to the dictionary, but you are using it more or less the other way around. – Peter B Nov 18 '16 at 11:44
  • @PeterB More details: it's a `DynamicObject` :D – Matías Fidemraizer Nov 18 '16 at 11:47
  • @PeterB It almost works like `ExpandoObject` but with compile-time properties – Matías Fidemraizer Nov 18 '16 at 11:48
  • Another duplicate: [Json.Net cannot serialize property of a class derived from dictionary](https://stackoverflow.com/questions/39084536/json-net-cannot-serialize-property-of-a-class-derived-from-dictionary). – dbc Nov 18 '16 at 17:27
  • @dbc It has nothing to do with my Q&A... – Matías Fidemraizer Nov 18 '16 at 20:06
  • As explained in that answer, if you mark your dictionary subtype with [`[JsonObject]`](http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_JsonObjectAttribute.htm) it will be deserialized as an object not a dictionary. Isn't that what you want? – dbc Nov 18 '16 at 21:59

2 Answers2

4

You can do it with custom contract resolver. Default contract resolver checks if class implements IDictionary and if so - serializes\deserializes it as such. You can change that:

class CustomResolver : DefaultContractResolver {
    protected override JsonContract CreateContract(Type objectType) {
        // if type implements your interface - serialize it as object
        if (typeof(IExtensibleObject).IsAssignableFrom(objectType)) {
            return base.CreateObjectContract(objectType);
        }

        return base.CreateContract(objectType);
    }
}

And then just:

 var settings = new JsonSerializerSettings(); // or change default settings
 settings.ContractResolver = new CustomResolver();                        
 Customer customer = JsonConvert.DeserializeObject<Customer>(@"{""Id"":""bcf66a92-00ea-4124-afa7-a6c200ae5886""}", settings);
Evk
  • 98,527
  • 8
  • 141
  • 191
1

You'll find an answer to this here : Duplicate Question

The only way I know is the same as answered in the link - you'll have to implement an custom JsonConverter

public class CustomerConverter : JsonConverter 
{
     public override bool CanConvert(Type objectType)
     {
        return objectType == typeof (Customer);
     }
     public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
      {
            var eobj = (Customer) value;
            var temp = new Dictionary<string, object>(eobj);
            temp.Add("Id", eobj.Id);
            serializer.Serialize(writer, temp);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
            JsonSerializer serializer)
        {
            var temp = serializer.Deserialize<Dictionary<string, object>>(reader);
            var eobj = new Customer();
            foreach (var key in temp.Keys)
            {
                if (key == "Id")
                    eobj.Id = (Guid) temp[key];
                else
                    eobj.Add(key, temp[key]);
            }
            return eobj;
        }
    }
Community
  • 1
  • 1
TripleEEE
  • 499
  • 3
  • 11
  • I've found another possible solution implementing a custom contract resolver which doesn't involve defining a new converter. And it's not flexible enough your solution because it's not just with `Id` but the issue affects all properties – Matías Fidemraizer Nov 18 '16 at 11:47
  • I still need to try it, if it works I'll post as an answer! – Matías Fidemraizer Nov 18 '16 at 11:49
  • Well you can define it with a contract resolver, but this will only define *which* properties will be serialized/deserialized - but will it change the **way** it is serialized? – TripleEEE Nov 18 '16 at 11:49
  • 1
    The contract resolver approach seems cleaner in my case, it worked flawlessly. BTW I've upvoted your answer both because your effort and because it might be a valid approach in other scenarios! – Matías Fidemraizer Nov 18 '16 at 12:03
  • 1
    Good to hear this would work out aswell, I didn't know that yet. I'll try this out aswell - maybe I'll need it sometime :) – TripleEEE Nov 18 '16 at 12:09