2014-08-29 76 views
2

在C#代碼中,我希望使用我設計的Int32屬性 - 作爲int,但是當使用Json.NET轉換爲Json時,我希望它們被序列化爲一個URI,所以我不必爲了json輸出而將我的所有模型映射到另一個模型。例如一個simpliflied型號:如何讓Json.NET將Int32轉換爲URI

public class Order 
{ 
    public int? AccountID { get; set; } 
    public int ProductID { get; set; } 
    public decimal Total { get; set; } 
} 

我想這是呈現:

{ "accountUri": "/account/123", "productUri": "/product/456", "total": 789.01 } 

注意外殼和財產改名發生了變化。

如果帳戶ID爲null,則JSON必須給予這樣的:

{ "productUri": "/product/456", "total": 789.01 } 

在C#代碼我仍然想使用屬性,你會正常的INT - 所以我想用INT運算符覆蓋。

我不想在模型屬性上使用屬性,但我很高興爲Int32使用包裝類,並且如果需要的話,不介意在包裝類上使用屬性。

下面的代碼是從回答一個公平的方式,但你得到的要點:

public class Order 
    { 
     public AccountIdentifier AccountID { get; set; } 
     public ProductIdentifier ProductID { get; set; } 
     public decimal Total { get; set; } 
    } 

    public abstract class IdentifierBase 
    { 
     private readonly string _uriPrefix; 
     private int? _value; 

     protected IdentifierBase(string uriPrefix, int? value) 
     { 
      _uriPrefix = uriPrefix; 
      _value = value; 
     } 

     public override string ToString() 
     { 
      if (_value.HasValue) 
       return _uriPrefix + _value.Value; 
      return null; 
     } 

     // insert int operator overrides here. 
    } 

    public class AccountIdentifier : IdentifierBase 
    { 
     public AccountIdentifier(int? value) 
      : base("/account/", value) 
     { 
     } 
    } 

    public class ProductIdentifier : IdentifierBase 
    { 
     public ProductIdentifier(int? value) 
      : base("/product/", value) 
     { 
     } 
    } 

    [Test] 
    public void JsonConvert() 
    { 
     var order = new Order 
     { 
      AccountID = new AccountIdentifier(123), 
      ProductID = new ProductIdentifier(456), 
      Total = 789.01M 
     }; 

     using (var stringWriter = new StringWriter()) 
     { 
      var writer = new JsonTextWriter(stringWriter) {Formatting = Formatting.None}; 
      var settings = new JsonSerializerSettings(); 
      var serializer = JsonSerializer.Create(settings); 

      // Camel case the properties. 
      serializer.ContractResolver = new CamelCasePropertyNamesContractResolver(); 

      serializer.Serialize(writer, order); 
      writer.Flush(); 
      var json = stringWriter.GetStringBuilder().ToString(); 
      Console.Write(json); 
     } 
    } 

此輸出:

{"accountID":{},"productID":{},"total":789.01} 

三個問題:

  1. 怎麼辦我將「accountID」重命名爲「accountUri」(並將「productID」重命名爲「productUri」)?

  2. 如何使這些特性與包裝類的ToString值(替換「{}」()的結果?

  3. 如何完全刪除屬性時,它的空?

感謝

編輯:儘管這是一個相當大量的工作,寫每個模型它保存寫兩個映射器的轉換器下面是我的概念驗證測試證明:

[TestFixture] 
    public class MyPoC 
    { 
     public class OrderJsonConverter : JsonConverter 
     { 
      public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
      { 
       writer.WriteStartObject(); 

       var order = value as Order; 

       if (order.AccountID.HasValue) 
       { 
        writer.WritePropertyName("accountUri"); 
        serializer.Serialize(writer, "/account/" + order.AccountID); 
       } 

       writer.WritePropertyName("productUri"); 
       serializer.Serialize(writer, "/product/" + order.ProductID); 

       writer.WritePropertyName("total"); 
       serializer.Serialize(writer, order.Total); 

       writer.WriteEndObject(); 
      } 

      public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
      { 
       var order = new Order(); 

       var jsonObject = JObject.Load(reader); 
       order.AccountID = jsonObject.GetNullableIntFromUri("accountUri"); 
       order.ProductID = jsonObject.GetIntFromUri("productUri"); 
       order.Total = jsonObject["total"].Value<decimal>(); 
       return order; 
      } 

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

     [Test] 
     public void JsonConvert_Is_Successful() 
     { 
      var order = new Order 
      { 
       AccountID = 123, 
       ProductID = 456, 
       Total = 789.01M 
      }; 

      var json = JsonConvert.SerializeObject(order, Formatting.None, new OrderJsonConverter()); 
      Console.WriteLine(json); 

      var deserialized = JsonConvert.DeserializeObject<Order>(json, new OrderJsonConverter()); 
      Console.WriteLine("AccountID: {0}", deserialized.AccountID); 
      Console.WriteLine("ProductID: {0}", deserialized.ProductID); 
      Console.WriteLine("Total: {0}", deserialized.Total); 
     } 
    } 
} 

public static class JObjectExtensions 
{ 
    public static int GetIntFromUri(this JObject jsonObject, string propertyName) 
    { 
     var id = jsonObject.GetNullableIntFromUri(propertyName); 
     return id.Value; 
    } 

    public static int? GetNullableIntFromUri(this JObject jsonObject, string propertyName) 
    { 
     var uri = jsonObject[propertyName].ToObject<string>(); 
     var s = Regex.Replace(uri, @".*/(\d+)$", "$1"); 
     int id; 
     if (int.TryParse(s, out id)) 
     { 
      return id; 
     } 
     return null; 
    } 
} 

OUTPUT:

{"accountUri":"/account/123","productUri":"/product/456","total":789.01} 
AccountID: 123 
ProductID: 456 
Total: 789.01 

更多的工作將是驗證URI是正確的,不只是一個通用的「翻錄從URI末尾的ID」。

+1

這裏適當的解決方案是使用適配器模式。雖然它不是技術上的映射,但我懷疑你不會因爲你的陳述而喜歡這個「所以我不必爲了json輸出而將我所有的模型映射到另一個模型」。你也應該查找關注點的分離。老實說,你應該真正重新考慮你對潛在解決方案的立場。你正在走上一條沒有什麼道理和難度的道路(並且會讓其他開發者感到困惑)。你已經完成了使用適配器模式,而不是等待在SO上的答案。 – 2014-08-29 13:56:51

+0

是的,我需要分開我的擔憂...我會考慮使用適配器模式。謝謝。 – mkaj 2014-08-30 00:08:22

+0

我已經添加了一個答案給你一個簡潔的例子。 – 2014-09-03 04:56:21

回答

1

既然你已經決定要考慮使用適配器模式,這裏是一個簡單的實現與測試:

[TestFixture] 
public class When_serializing_Order 
{ 
    [SetUp] 
    public void SetUp() 
    { 
     JsonConvert.DefaultSettings =() => new JsonSerializerSettings() 
     { 
      ContractResolver = new CamelCasePropertyNamesContractResolver(), 
      NullValueHandling = NullValueHandling.Ignore 
     }; 
    } 

    [TestCase(123, 456, 789.01, "{\"accountUri\":\"/account/123\",\"productUri\":\"/product/456\",\"total\":789.01}")] 
    [TestCase(null, 456, 789.01, "{\"productUri\":\"/product/456\",\"total\":789.01}")] 
    public void Should_render_exact_json(int? accountId, int productId, decimal total, string expectedJson) 
    { 
     var order = new Order 
     { 
      AccountID = accountId, 
      ProductID = productId, 
      Total = total 
     }; 
     string jsonOrder = JsonConvert.SerializeObject(new OrderAdapter(order)); 
     Assert.That(jsonOrder, Is.EqualTo(expectedJson)); 
    } 
} 

public class Order 
{ 
    public int? AccountID { get; set; } 
    public int ProductID { get; set; } 
    public decimal Total { get; set; } 
} 

public class OrderAdapter 
{ 
    private readonly Uri _accountUri; 
    private readonly Uri _productUri; 
    private readonly decimal _total; 

    public OrderAdapter(Order order) 
    { 
     _accountUri = order.AccountID != null ? CreateRelativeUri("account", order.AccountID.Value) : null; 
     _productUri = CreateRelativeUri("product", order.ProductID); 
     _total = order.Total; 
    } 

    public Uri AccountUri { get { return _accountUri; } } 
    public Uri ProductUri { get { return _productUri; } } 
    public decimal Total { get { return _total; } } 

    private Uri CreateRelativeUri(string resourceType, int id) 
    { 
     return new Uri(String.Format("/{0}/{1}", resourceType, id), UriKind.Relative); 
    } 
} 

如果您有任何疑問,請與評論張貼到,我會批註任何需要進一步解釋。

+0

偉大的解決方案。滿足我的所有要求... esp沒有任何屬性。謝謝。 – mkaj 2014-09-03 19:36:03

4

1)使用JsonProperty屬性的屬性名的參數,如:

[JsonProperty(PropertyName = "accountUri")] 
public AccountIdentifier AccountID { get; set; } 

2){}是AccountIdentifier和產品識別類的內容。您需要爲Order類編寫一個自定義Json轉換器,以便自定義(反)序列化內容。

以下是相關的JSON.NET documentation

3)你也可以通過JsonProperty進行設置。

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] 

編輯:這裏是一個自定義JsonConvert對Order類的一部分。它不使用上面提到的屬性,而是手動處理它。如果其他人可以提供更好(更完整)的解決方案,請做。

public class OrderJsonConverter : JsonConverter 
{ 
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     writer.WriteStartObject(); 

     var obj = value as Order; 

     writer.WritePropertyName("accountUri"); 
     serializer.Serialize(writer, obj.AccountID.ToString()); 

     writer.WritePropertyName("productUri"); 
     serializer.Serialize(writer, obj.ProductID.ToString()); 

     writer.WritePropertyName("Total"); 
     serializer.Serialize(writer, obj.Total); 

     writer.WriteEndObject(); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     throw new NotImplementedException(); 
    } 

    public override bool CanConvert(Type objectType) 
    { 
     return typeof(Order).IsAssignableFrom(objectType); 
    } 
} 
+0

您的編輯是一個很好的答案,謝謝!編寫所有覆蓋的工​​作很多,但比我的第一個答案更可接受,因爲我不想使用屬性。謝謝! – mkaj 2014-08-30 00:12:22