Patch to fix arrays with 1 element when converting from xml to json and deserializing

Nov 11, 2011 at 10:56 AM

Problems occur when converting from xml to json as there is no good way of denoting empty arrays and arrays with 1 element.

Perhaps we could assume that if the class we are deserializing to has a property that is an array then we want to convert the json single object into the array. When deserializing it checks to see if the type it is returning matches the .net object property type. If it does not match it tries to convert it to a compatible type. So this is probably the best point to swap it over to an array. You could probably use the same strategy when dealing with IEnumerable<T> too.

Fix for default conversions when dealing with arrays (ConvertUtils.cs)

    /// <summary>
    /// Converts the value to the specified type. If the value is unable to be converted, the
    /// value is checked whether it assignable to the specified type.
    /// </summary>
    /// <param name="initialValue">The value to convert.</param>
    /// <param name="culture">The culture to use when converting.</param>
    /// <param name="targetType">The type to convert or cast the value to.</param>
    /// <returns>
    /// The converted type. If conversion was unsuccessful, the initial value
    /// is returned if assignable to the target type.
    /// </returns>
    public static object ConvertOrCast(object initialValue, CultureInfo culture, Type targetType)
    {
      object convertedValue;

      if (targetType == typeof(object))
        return initialValue;

      if (initialValue == null && ReflectionUtils.IsNullable(targetType))
        return null;

	  //Array with 0 entries
	  if (initialValue == null && targetType.IsArray)
	  {
		  return Array.CreateInstance(targetType.GetElementType(), 0);
	  }

	  //Array with 1 entry
	  if (initialValue != null && targetType.IsArray && targetType.GetElementType() == initialValue.GetType())
	  {
		  var array = Array.CreateInstance(targetType.GetElementType(), 1);
		  array.SetValue(initialValue, 0);
		  return array;
	  }

      if (TryConvert(initialValue, culture, targetType, out convertedValue))
        return convertedValue;

      return EnsureTypeAssignable(initialValue, ReflectionUtils.GetObjectType(initialValue), targetType);
    }

 

 

Test 1 - Array with 0 element

[Test]
	  public void ToXmlThenToJson0ItemInChildCollection()
	  {
	  	var product = new Product();

	  	product.Name = "Apple";
	  	product.ExpiryDate = new DateTime(2008, 12, 28);
	  	product.Price = 3.99M;
	  	product.Sizes = new string[] {};

		var jsonText = JsonConvert.SerializeObject(product, Formatting.Indented);
		var xmlDocument = JsonConvert.DeserializeXmlNode(jsonText, "root");
		var jsonText2 = JsonConvert.SerializeXmlNode(xmlDocument, Formatting.Indented, true);

	  	var deserializer = JsonSerializer.Create(new JsonSerializerSettings
	  	                                         	{
														ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
														ContractResolver = new DefaultContractResolver
														{
															DefaultMembersSearchFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
														},
														TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple,
			
	  	                                         		NullValueHandling = NullValueHandling.Ignore,
	  	                                         		DefaultValueHandling = DefaultValueHandling.Ignore,
	  	                                         		MissingMemberHandling = MissingMemberHandling.Ignore,
	  	                                         		ObjectCreationHandling = ObjectCreationHandling.Auto,
	  	                                         		ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
	  	                                         	});

	  	Product result;
	  	using (var stringReader = new StringReader(jsonText2.ToString()))
	  	using (var jsonReader = new JsonTextReader(stringReader))
	  	{
	  		result = deserializer.Deserialize<Product>(jsonReader);
	  	}

	  	Assert.AreEqual(product.Sizes.Count(), result.Sizes.Count());
	  }

Test 2 - Array with 1 element

[Test]
	  public void ToXmlThenToJson1ItemInChildCollection()
	  {
		  var product = new Product();

		  product.Name = "Apple";
		  product.ExpiryDate = new DateTime(2008, 12, 28);
		  product.Price = 3.99M;
		  product.Sizes = new string[] { "Small" };

		  var jsonText = JsonConvert.SerializeObject(product, Formatting.Indented);
		  var xmlDocument = JsonConvert.DeserializeXmlNode(jsonText, "root");
		  var jsonText2 = JsonConvert.SerializeXmlNode(xmlDocument, Formatting.Indented, true);

		  var deserializer = JsonSerializer.Create(new JsonSerializerSettings
		  {
			  ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
			  ContractResolver = new DefaultContractResolver
			  {
				  DefaultMembersSearchFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
			  },
			  TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple,

			  NullValueHandling = NullValueHandling.Ignore,
			  DefaultValueHandling = DefaultValueHandling.Ignore,
			  MissingMemberHandling = MissingMemberHandling.Ignore,
			  ObjectCreationHandling = ObjectCreationHandling.Auto,
			  ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
		  });

		  Product result;
		  using (var stringReader = new StringReader(jsonText2.ToString()))
		  using (var jsonReader = new JsonTextReader(stringReader))
		  {
			  result = deserializer.Deserialize<Product>(jsonReader);
		  }

		  Assert.AreEqual(product.Sizes.Count(), result.Sizes.Count());
	  }

Test 3 - Array with 2 elements

 [Test]
	  public void ToXmlThenToJson2ItemInChildCollection()
	  {
		  var product = new Product();

		  product.Name = "Apple";
		  product.ExpiryDate = new DateTime(2008, 12, 28);
		  product.Price = 3.99M;
		  product.Sizes = new string[] { "Small", "big" };

		  var jsonText = JsonConvert.SerializeObject(product, Formatting.Indented);
		  var xmlDocument = JsonConvert.DeserializeXmlNode(jsonText, "root");
		  var jsonText2 = JsonConvert.SerializeXmlNode(xmlDocument, Formatting.Indented, true);

		  var deserializer = JsonSerializer.Create(new JsonSerializerSettings
		  {
			  NullValueHandling = NullValueHandling.Ignore,
			  DefaultValueHandling = DefaultValueHandling.Ignore,
			  MissingMemberHandling = MissingMemberHandling.Ignore,
			  ObjectCreationHandling = ObjectCreationHandling.Auto,
			  ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
		  });

		  Product result;
		  using (var stringReader = new StringReader(jsonText2))
		  using (var jsonReader = new JsonTextReader(stringReader))
		  {
			  result = deserializer.Deserialize<Product>(jsonReader);
		  }

		  Assert.AreEqual(product.Sizes.Count(), result.Sizes.Count());
	  }

Maybe fix ConvertUtils.cs

Nov 14, 2011 at 12:21 PM

This hack would only work for arrays. I have fixed the deep rooted problem in a patch, which will fix deserialization for arrays and collections.

See:

Patch Id: 10832
http://json.codeplex.com/SourceControl/list/patches?page=0