0

(this issue stems from trying to serialize/deserialize LikeType classes to JSON - https://github.com/kleinwareio/LikeType)

I have:

    public abstract class LikeType<T>
    {
      public T Value;
      // ....

      // how to tell json.net to serialize/deserialize classes deriving
      // from this like it would T ???
    }

    public class Name : LikeType<string>  { 
      public Name(string s) : base(s) { } 
      // does not add any properties
    }

    void test()
    {
      var name = new Name("john");
      var jobj = new JObject();
      try
      {
        jobj.Add("key", new JObject(name));
      }
      catch (Exception e)
      {
         !Exeption !
         e = {System.ArgumentException: Could not determine JSON object type for type Name. at Newtonsoft.Json.Linq.JValue.GetValueType(Nullable`1 current, Object value) at  Newtonsoft.Json.Linq.JContainer.CreateFromContent(Object content)
      }
    }

How can I specify that all classes deriving from LikeType<T> will be serialized/ deserialized to JSON with Json.Net in the same way T would?

(in this case, Json.Net should serialize/deserialize Name in the same way it would a string)

kofifus
  • 17,260
  • 17
  • 99
  • 173
  • 1
    This makes little sense to me. Could you provide some examples where you serialize different instances of `LikeType`? What should the output be when the type argument is `double` or `DateTime`? – Freggar Jun 22 '18 at 09:53
  • exactly the same as Json.Net would serialize double or DateTime. I want to serialize LikeType in the default way for T – kofifus Jun 22 '18 at 09:55
  • That works out of the box, just do `JsonConvert.SerializeObject(new Name("test"));` I honestly don't understand what your actual *issue* is here. What does not behave as you expect it to? – Freggar Jun 22 '18 at 10:01
  • 1
    Can you please post an [mcve], the json it produces, and what you want to change about it? I don't understand what you mean by "like it would T". Json.Net **does** serialize generic types, **just as it serializes T**, can you please clarify exactly what the problem is? – Lasse V. Karlsen Jun 22 '18 at 10:07
  • thx, I added test code showing the exception I get – kofifus Jun 22 '18 at 11:03

2 Answers2

1

I believe you want to "forward" LikeType<T> serialization, treating this like an invisible wrapper type. This assumption is crucial to my solution.

I'd suggest using JsonConverter implementation to do that. There is a very similar post here: Json.NET - Serialize generic type wrapper without property name

I've adapted the example to your case. This is the adapted approach:

class LikeTypeConverter : JsonConverter
{
    static Type GetValueType(Type objectType)
    {
        return objectType
            .BaseTypesAndSelf()
            .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(LikeType<>))
            .Select(t => t.GetGenericArguments()[0])
            .FirstOrDefault();
    }

    public override bool CanConvert(Type objectType)
    {
        return GetValueType(objectType) != null;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // You need to decide whether a null JSON token results in a null LikeType<T> or 
        // an allocated LikeType<T> with a null Value.
        if (reader.SkipComments().TokenType == JsonToken.Null)
            return null;
        var valueType = GetValueType(objectType);
        var value = serializer.Deserialize(reader, valueType);

        // Here we assume that every subclass of LikeType<T> has a constructor with a single argument, of type T.
        return Activator.CreateInstance(objectType, value);
    }

    const string ValuePropertyName = "Value";// nameof(LikeType<object>.Value); // in C#6+

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
        var valueProperty = contract.Properties.Single(p => p.UnderlyingName == ValuePropertyName);
        serializer.Serialize(writer, valueProperty.ValueProvider.GetValue(value));
    }
}

public static partial class JsonExtensions
{
    public static JsonReader SkipComments(this JsonReader reader)
    {
        while (reader.TokenType == JsonToken.Comment && reader.Read())
        {
        }

        return reader;
    }
}

public static class TypeExtensions
{
    public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
    {
        while (type != null)
        {
            yield return type;
            type = type.BaseType;
        }
    }
}

You can use this as an attribute on LikeType<T> declaration if you want to include this in your library:

[JsonConverter(typeof(LikeTypeConverter))]
public abstract class LikeType<T> { ... }

Or you can use the converter when necessary, modifying JsonSerializerSettings.Converters collection:

    var settings = new JsonSerializerSettings
    {
        Converters = { new LikeTypeConverter() },
        ContractResolver = new CamelCasePropertyNamesContractResolver()
    };
    var result = JsonConvert.SerializeObject(myObject, Formatting.Indented, settings);

I've also created a working dotnetfiddle sample for demonstration (also adapting the one from linked post).

Amiś
  • 179
  • 1
  • 5
  • thank you !! I cannot get it to work with the attribute :( I added a test to your fiddle here https://dotnetfiddle.net/mo0Imh what's wrong ? – kofifus Jun 23 '18 at 22:53
  • ah I got it, I needed JObject.FromObject – kofifus Jun 23 '18 at 22:58
  • question, in .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(LikeType<>)) - can we have a way to not specify the class there (LikeType<>) so we can have a general way to serialize classes that proxy another class through a Value property ? – kofifus Jul 10 '18 at 23:23
  • @kofifus Possibly. But you'd need another way to extract the Type of that Value property. Maybe you could instead do `objectType.GetProperty("Value")?.PropertyType` if you're always looking for that single property. It can be a dangerous pattern, because this'd also work for all Nullable types. :) – Amiś Jul 12 '18 at 08:15
  • I did find a way and also added using an empty ctor if the other one is not there see https://stackoverflow.com/a/51295920/460084 – kofifus Jul 17 '18 at 01:50
0

One way of controlling what most serializers serialize is using the Serializable attribute and implement the ISerializable interface.

Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59