Search code examples
serializationc#-4.0dojojson.netunions

How to serialize a "union-like" field in C# with Json.NET


I am attempting to generate a JSON file that will be used within the Dojo javascript framework and would like to return a position attribute to be used in a dojo.place() call. The position parameter can be either a number or a string.

Using the StructLayoutwould not seem to work as-is since the serializer would try to emit both the String and Integer types. I'm looking at creating a custom ContractResolver that overrides the CreatePrimitiveContract to return a custom JsonConverter class. However, looking a the API, it appears that the JsonConverter is created based on type, and not a specific object value.

How can I handle this case in C# using the Json.NET serializer?

Presumably the solution would involve two properties with custom setters to null out the other property when one is set in conjunction with some sort of custom Json.Net class to inspect the values of the properties and only serialize the non-null one.

** Hypothetical Example **

// C# struct (or class)
[StructLayout(LayoutKind.Explicit)]
struct DojoPosition {
   [JsonProperty(PropertyName="position")]
   [FieldOffset(0)]
   public String StrPos;

   [JsonProperty(PropertyName="position")]
   [FieldOffset(0)]
   public Int32 IntPos;
}

// Serialization output
DojoPosition pos;
pos.StrPos = "only";
var output = JsonConvert.SerializeObject(pos);

// Output is: { "position": "only" }

pos.IntPos = 3;
var output = JsonConvert.SerializeObject(pos);

// Output is: { "position": 3 }

Solution

  • I just had a similiar problem. For simple manipulation of a contract look there: Overriding the serialization behaviour in Json.Net

    For resolving a JsonPrimitiveContract override the CreateContract method.

    Here is an example based on our solution:

       public class JsonDotNetContractResolver : DefaultContractResolver
       {
          protected override JsonContract CreateContract(Type objectType)
          {
             if (typeof(DojoPosition).IsAssignableFrom(objectType))
             {
                return new JsonPrimitiveContract(objectType.GetGenericArguments()[1])
                          {
                             CreatedType = typeof(object), // Not sure this will work for you, or is necessary...
                             IsReference = false,
                             Converter = DojoPositionConverter,
                          };
             }
             return base.CreateContract(objectType);
          }
          private class DojoPositionConverter : JsonConverter
          {
             public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
             {
                var dp = (DojoPosition) value;
                if(string.IsNullOrEmpty(dp.StrPos))
                   serializer.Serialize(writer,dp.IntPos);
                else
                   serializer.Serialize(writer,dp.StrPos);
             }
             public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
             {
                //...
             }
             public override bool CanConvert(Type objectType)
             {
                //....
             }
          }      
       }
    

    How to determine the type to deserialize from the reader is your homework ;)