2012-09-23 47 views
2

我使用Json.Net。 當我序列化一個Department2對象,並調用WriteJson()我希望它與對象中的每一個遞歸調用,就像我在ReadJson()中做的那樣。如何遞歸調用WriteJson?

我該怎麼做?

using System; 
using Newtonsoft.Json; 
using Newtonsoft.Json.Linq; 

public interface ISimpleDatabag 
{ 
    string Databag { get; set; } 
} 

[JsonConverter(typeof(JsonDataBagCreationConverter<Department2>))] 
public class Department2 
{ 
    public Telephone2[] Phones { get; set; } 
} 

[JsonConverter(typeof(JsonDataBagCreationConverter<Telephone2>))] 
public class Telephone2 
{ 
    public string Name { get; set; } 
    public string AreaCode { get; set; } 
    public string Number { get; set; } 
} 

public class JsonDataBagCreationConverter<T> : JsonConverter where T : new() 
{ 
    // Json.Net version 4.5.7.15008 
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     // When I serialize Department and this function is invoked 
     // I want it to recursively invoke WriteJson with each of the Telephone objects 
     // Like I do in ReadJson 
     // How do I do that? 
     T t = (T)value; 
     serializer.Serialize(writer, t.GetType()); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     var jsonObject = JObject.Load(reader); 
     var target = Create(objectType, jsonObject); 
     serializer.Populate(jsonObject.CreateReader(), target); // Will call this function recursively for any objects that have JsonDataBagCreationConverter as attribute 
     return target; 
    } 

    protected T Create(Type objectType, JObject jsonObject) 
    { 
     return new T(); 
    } 

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


private void Form1_Load(object sender, EventArgs e) 
     { 
      string jsonInput = "{\"Name\": \"Seek4\" , \"CustomDepartmentData\": \"This is custom department data\", \"Phones\":[ {\"Name\": \"A\", \"AreaCode\":444, \"Number\":11111111} ,{\"Name\": \"B\", \"AreaCode\":555, \"Number\":987987987}, {\"Name\": \"C\", \"AreaCode\":222, \"Number\":123123123, \"CustomPhoneData\": \"This is custom phone data\"} ] }"; 
      Department2 objDepartment2 = JsonConvert.DeserializeObject<Department2>(jsonInput); // Yes, it works well 
      Array.Reverse(objDepartment2.Phones); 
      string jsonNoDatabag = JsonConvert.SerializeObject(objDepartment2); 
     } 

回答

1

我最終使用這個巨大的(未重構的)函數來控制整個過程。 我基本上調查了要序列化的對象的每個屬性,然後按屬性序列化它的屬性。 然後我可以做每個屬性上的定製東西

/// <summary> 
/// Serializes an object by merging its current values into its databag and returns the databag 
    /// </summary> 
    /// <param name="objectToSerialize"></param> 
    /// <returns>the current values merged into the original databag</returns> 
    /// <remarks>Jan Nielsen, 01-10-2012</remarks> 
    internal static string SerializeObjectToDatabag(object objectToSerialize) 
    { 
     // You have to do it property by property instead of just serializing the entire object and merge it into the original 
     // because the object might contain lists of objects with custom data and these list might have been sorted differently from when they were loaded 
     // So you cannot merge them properly unless you do it on a per listitem basis. 
     // Which is what I do here. 
     try 
     { 

      if (objectToSerialize == null) // If you ie serialize an empty object in an array 
      { 
       return null; 
      } 

      string updatedDatabag = ""; 
      bool isIDataBag = objectToSerialize is IDataBag; 
      if (isIDataBag) 
      { 
       updatedDatabag = ((IDataBag)objectToSerialize).Data == null ? "" : ((IDataBag)objectToSerialize).Data.ToString(); 
       // updatedDatabag = ((IDataBag)objectToSerialize).Data.ToString(); // Save original data in a local variable. This is the one we will merge new values into 
      } 
      string result = ""; 
      // Now iterate through the objects properties 
      // Possible properties: 
      // Simple types: string, int, bool etc: their current value should be overwritten in the databag 
      // types that implement IDatabag: they should be sent to this function recursively so their possible customdata is not overwritten 
      // but instead their simple values are merged into their own databag. Then the result of this single property merge is overwritten in the outer objects databag 
      // Types that are not simple and don't implement IDatabag but have properties that implement IDatabag 
      // types that are not simple and don't implement IDatabag and don't have any properties in any depth that implement IDatabag: They are overwritten in the databag 
      // Types that are arrays: 
      //  If the types in the array are simple types (string, bool etc) the entire array property is overwritten in the databag 
      //  If the types in the array implement IDatabag each object is sent recursively to this function and their databag is updated via merge 
      //  Then the entire array is overwritten in the outer objects databag 
      // Types that are generic list are treated like arrays 

      var properties = objectToSerialize.GetType().GetProperties(); 

      // In order to be able to deserialize abstract classes and interfaces, we need to serialize the classname with the class 
      // the deserializer recognizes the word $type followed by a type, when its is invoked with a serializerSettings of 
      // serializerSettings.TypeNameHandling = TypeNameHandling.Objects; 
      string name = objectToSerialize.GetType().AssemblyQualifiedName; 
      string shortName = RemoveAssemblyDetails(name); 
      bool addedType = false; 

      foreach (var propertyInfo in properties) 
      { 
       if (propertyInfo.Name.ToLower() != "data") // Just skip Databag. Databag is not a "real" property but the contents of all the properties when the object was loaded + possible custom data 
       { 
        if (!addedType) 
        { 
         string jsonSingleProperty = "{ " + ToCustomJson("$type") + " : " + ToCustomJson(shortName) + " }"; 
         // Merge the current value (jsonSingleProperty) into the databag (that might already have been updated with the values of other properties) 
         // and update the current result with the new values. Ie "Name" : "Seek4" is updated to "Name" : "Seek4Cars" in the databag 
         // and the function will now use the updated databag to merge the other properties into 
         updatedDatabag = MergeDefault(jsonSingleProperty, updatedDatabag, true); 
         addedType = true; 
        } 
        // propertyInfo.Name.ToLower().Contains("struct") 
        var value = propertyInfo.GetValue(objectToSerialize, null); // This gets the value of the specified property in the current object 

        isIDataBag = value is IDataBag; // Update for the current object. Note that ie an array of IDatabag will return false here, because array is not IsimpleDatabag 

        // Basically we should just check if the property implements IDatabag 
        // But the simpletype check is faster because I don't have to check for the interfaces on ie a string, int etc. 
        // This branch takes care of 3 cases: 
        // 1) it is a simple type, ie int 
        // 2) value is null 
        // 3) it is an array with a value of null 
        // If an array with values enters this branch of code the values of the array will be appended, overwritten 
        // Therefore arrays are treated below in a special case. Unless they are null 
        // GeneralFunctions.IsExtendedSimpleType_AllTypes(propertyInfo.PropertyType) returns true on ie string[], but only arrays with a value of null should be handled here 

        // This first check originally just checked for simple types 
        // Then it became extended simple types ie non-simple types that only contains simple types ie List<int,int> 
        // But not arrays that must be handled separately 
        // Then it also handled null values 
        // And then a special case was made for arrays that are null 

        if ((GeneralFunctions.IsExtendedSimpleType_AllTypes(propertyInfo.PropertyType) || value == null) && (!propertyInfo.PropertyType.IsArray || (propertyInfo.PropertyType.IsArray && value == null))) 
        { 
         // You have to merge even though it is default value. 
         // If you have ie a bool that has an initial value of true and you deliberately sets it to false 
         // You want the defaultvalue of false to be merged into the json. 

         string jsonSingleProperty = "{" + ToCustomJson(propertyInfo.Name) + " : " + ToCustomJson(value) + "}"; // ie {"Name" : "Seek4Cars"} 
         // Merge the current value (jsonSingleProperty) into the databag (that might already have been updated with the values of other properties) 
         // and update the current result with the new values. Ie "Name" : "Seek4" is updated to "Name" : "Seek4Cars" in the databag 
         // and the function will now use the updated databag to merge the other properties into 
         updatedDatabag = MergeDefault(jsonSingleProperty, updatedDatabag, true); 
         continue; 
        } 

        if (isIDataBag) // ie PhoneSingle. A single property of type IDataBag 
        { 
         // Invoke recursively 

         // First check if this is an object with all null values 
         bool allPropertiesAreNull = true; // Maybe this should in the future be expanded with a check on if the property has its default value ie an int property with a value of 0 
         foreach (var propertyInfoLocal in value.GetType().GetProperties()) 
         { 
          var valueLocal = propertyInfoLocal.GetValue(value, null); 
          if (valueLocal != null) 
          { 
           allPropertiesAreNull = false; 
           break; 
          } 
         } 
         var testjson = ""; 
         if (allPropertiesAreNull) 
         { 
          result = "{" + ToCustomJson(propertyInfo.Name) + " : " + " { } }"; 
         } 
         else 
         { 
          testjson = ToCustomJson(value); 
          result = "{" + ToCustomJson(propertyInfo.Name) + " : " + SerializeObjectToDatabag(value) + "}"; 
         } 

         updatedDatabag = MergeDefault(result, updatedDatabag, true); 
         continue; 
        } 

        bool containsIDataBag = CheckForDatabagInterfaces.ImplementsInterface(propertyInfo.PropertyType, "idatabag"); // Check if anything inside the property implements IDatabag ie an array of IDatabag 

        if (containsIDataBag) 
        { 
         // Check if it is somekind of generic list (List<T>, Dictionary<T,T) etc) and if it is a type of ignoreTypes ie List<entity>) 
         if (value.GetType().IsGenericType && value.GetType().GetGenericArguments().Length > 0) 
         { 
          string listValuesAsJson = ""; 
          if (value is IEnumerable) 
          { 
           listValuesAsJson += "{ " + ToCustomJson(propertyInfo.Name) + " : ["; 
           bool containsItems = false; 
           foreach (var element in (IEnumerable)value) 
           { 
            containsItems = true; 
            var current = SerializeObjectToDatabag(element); 
            if (current != null) // If you serialize an empty array element it is null 
            { 
             listValuesAsJson += current + ", "; // Add , between each element 
            } 
           } 
           if (containsItems) 
           { 
            listValuesAsJson = listValuesAsJson.Substring(0, listValuesAsJson.Length - 2) + "] }"; // remove last , and add ending ] for the array and add a } because this property is flowing in the free 
           } 
           else // No items in value 
           { 
            listValuesAsJson += "] }"; // add ending ] for the array and add a } because this property is flowing in the free 
           } 
          } 
          else // A single, generic KeyValuePair property 
          { 
           listValuesAsJson += "{ " + ToCustomJson(propertyInfo.Name) + " : "; 
           listValuesAsJson += SerializeObjectToDatabag(value); 
           listValuesAsJson += " }"; 
          } 

          updatedDatabag = MergeDefault(listValuesAsJson, updatedDatabag, false); 
         } 
         else if (value.GetType().IsArray) 
         { 
          string arrayValuesAsJson = "{ " + ToCustomJson(propertyInfo.Name) + " : ["; 
          bool containsItems = false; 
          foreach (var element in (Array)value) 
          { 
           // Treat them the same way you treat any other object 
           var current = SerializeObjectToDatabag(element); 
           if (current != null) // If you serialize an empty array element it is null 
           { 
            containsItems = true; 
            arrayValuesAsJson += current + ", "; 
           } 
          } 
          if (containsItems) 
          { 
           arrayValuesAsJson = arrayValuesAsJson.Substring(0, arrayValuesAsJson.Length - 2) + "] }"; // remove last , and add ending ] for the array and add a } because this property is flowing in the free 
          } 
          else // No items in value 
          { 
           arrayValuesAsJson += "] }"; // add ending ] for the array and add a } because this property is flowing in the free 
          } 
          updatedDatabag = MergeDefault(arrayValuesAsJson, updatedDatabag, false); 
         } 
         else if (value.GetType().BaseType != null && value.GetType().BaseType.FullName.ToLower().Contains("system.collections.objectmodel")) 
         { 
          // This branch was made specifically to take care of the Media collection of a Seek4.Entities.V2.Media.MediaCollection 
          var genericList = (IList)value; 
          int counter = genericList.Count; 

          string listAsJson = "{ " + ToCustomJson(propertyInfo.Name) + " : ["; 
          if (counter == 0) 
          { 
           listAsJson += "] }"; // Ie { "Media": [] } 
          } 
          else 
          { 
           foreach (var obj in genericList) 
           { 
            var current = SerializeObjectToDatabag(obj); 
            listAsJson += current + ", "; 
           } 
           listAsJson = listAsJson.Substring(0, listAsJson.Length -2) + " ] }" ; 
          } 
          updatedDatabag = MergeDefault(listAsJson, updatedDatabag, true); // hvordan gør json.net dette med standard? 

         } 
         else // a single Non-IDatabag property that contains Idatabag properties 
         { 
          string tempResult = "{ " + ToCustomJson(propertyInfo.Name) + " : "; 
          tempResult += SerializeObjectToDatabag(value) + " }"; 
          updatedDatabag = MergeDefault(tempResult, updatedDatabag, true); 
         } 
        } 
        else 
        { 
         if (value.GetType().IsArray) // This is an array of simple types so just overwrite 
         { 
          string arrayAsJson = "{ " + ToCustomJson(propertyInfo.Name) + " : "; 
          arrayAsJson += ToCustomJson(value) + "}"; 
          updatedDatabag = MergeDefault(arrayAsJson, updatedDatabag, false); 
         } 
         else // ie an object that is not simpledatabag and doesn't contain simple databag 
         { 
          string jsonSingleProperty = "{" + ToCustomJson(propertyInfo.Name) + " : " + ToCustomJson(value) + "}"; 
          updatedDatabag = MergeDefault(jsonSingleProperty, updatedDatabag, true); 
         } 
        } 
       } 
      } 
      return updatedDatabag; 
     } 
     catch (Exception ex) 
     { 
      string message = ex.Message; 
      string stack = ex.StackTrace; 
      throw; 
     } 
    } 



internal static string ToCustomJson(object objectToConvertToJson) 
    { 
     try 
     { 
      // Distinguished from Mongodb.Bson.ToJson() extensionmethod by Custom name 
      JsonSerializerSettings serializerSettings = new JsonSerializerSettings(); 
      serializerSettings.TypeNameHandling = TypeNameHandling.Objects; // Adds a $type on all objects which we need when it is abstract classes and interfaces 
      IgnoreDataMemberContractResolver contractResolver = new IgnoreDataMemberContractResolver(null, true, true); 
      serializerSettings.ContractResolver = contractResolver; 
      serializerSettings.DefaultValueHandling = DefaultValueHandling.Ignore; 
      IsoDateTimeConverter converter = new IsoDateTimeConverter(); 
      serializerSettings.Converters.Add(converter); 
      string result = JsonConvert.SerializeObject(objectToConvertToJson, Formatting.None, serializerSettings); 
      return result; 
     } 
     catch (Exception ex) 
     { 
      throw new Exception("Error in ToCustomJson: " + ex.Message, ex); 
     } 
    }