简体   繁体   中英

Is there a way to get JSON.Net to serialize an object using implicit conversion instead of its actual type?

Consider this interface and classes...

public interface IValueHolder{}

public class ValueHolder<TValue> : IValueHolder {

    public ValueHolder(TValue value) => this.value = value;
    public TValue value { get; }

    public static implicit operator ValueHolder<TValue>(TValue value) => new ValueHolder<TValue>(value);
    public static implicit operator TValue(ValueHolder<TValue> valueHolder) => valueHolder.value;
}

class ValueStorage : Dictionary<string, IValueHolder>{} 

Note the implicit conversions to and from TValue .

The point of this code is I'm trying to store key/value pairs where the value can be any type, but without having boxing penalties. This is possible since I will always know the type when setting or retrieving the value from the storage. (That's why I didn't just use Dictionary<string, object> .)

Now consider this code...

var storage = new ValueStorage();

storage["UltimateQuestion"] = new ValueHolder<string>("What do you get when you multiply six by nine?");
storage["UltimateAnswer"] = new ValueHolder<int>(42);

var jsonSerializerSettings = new JsonSerializerSettings{
    TypeNameHandling  = TypeNameHandling.None,
    Formatting        = Formatting.Indented,
    NullValueHandling = NullValueHandling.Ignore
};

var jsonString = JsonConvert.SerializeObject(storage, jsonSerializerSettings);

The result is this...

{
    "UltimateQuestion": {
        "value": "What do you get when you multiply six by nine?"
    },
    "UltimateAnswer": {
        "value": 42
    }
}

I was hoping with the implicit conversion to and from TValue , which are string and int respectively, it would give me this...

{
    "UltimateQuestion": "What do you get when you multiply six by nine?",
    "UltimateAnswer": 42
}

So how can I get ValueStorage<T> to serialize and deserialize as T ? I don't mind writing custom converters (provided they're generic and can be based on TValue ) or custom SettingsContractResolver subclasses, but I'm not sure where to start.

Update

The more I think about this, the more I think this isn't actually solvable. This is because while serialization is easy, the deserialization would need to know the types for TValue , which aren't stored in the JSON in the format that I want, therefore it can't be deserialized. (Even the original output above with the nested objects has the same issue. You need the type information for this to work in any capacity.)

However, as not to keep this question open, what about a converter that stores the type information for TValue instead of ValueHolder<TValue> ?

eg

{
    "UltimateQuestion": {
        "$type": "[Whatever TValue is--string here]",
        "value": "What do you get when you multiply six by nine?"
    },
    "UltimateAnswer": {
        "$type": "[Whatever TValue is--int here]",
        "value": 42
    }
}

That way the converter could reconstruct ValueHolder<TValue> instances during deserialization. It's not perfect, but would at least allow proper deserialization without exposing the ValueHolder framework.

You can write a converter (see https://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm ).

public class ValueHolderJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
      serializer.Serialize(writer, ((IValueHolder)value).value);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
      return Activator.CreateInstance(objectType, serializer.Deserialize(reader));
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(IValueHolder).IsAssignableFrom(objectType);
    }
}

Please not that I added a non-generic "value" property to your interface to get the values regardless of the actual generic type without needing to use reflection:

public interface IValueHolder{
   object value { get; }
}

public class ValueHolder<TValue> : IValueHolder
{
    // as before

    object IValueHolder.value => (object)value;
}

To use it you create a new JsonSerializerSettings class (or append your existing one):

var settings = new JsonSerializerSettings {
    Converters = {
        new ValueHolderJsonConverter()
    };

And provide it as parameter to SerializeObject and DeserializeObject.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM