C# 如何让Json.NET将Int32转换为URI

C# 如何让Json.NET将Int32转换为URI,c#,json.net,C#,Json.net,在C#代码中,我希望使用设计为int的Int32属性,但在使用Json.NET转换为Json时,我希望将它们序列化为URI,这样我就不必将所有模型映射到另一个模型,只用于Json输出。e、 g.简化模型: public class Order { public int? AccountID { get; set; } public int ProductID { get; set; } public decimal Total { get; set; } } 我想把它呈

在C#代码中,我希望使用设计为int的Int32属性,但在使用Json.NET转换为Json时,我希望将它们序列化为URI,这样我就不必将所有模型映射到另一个模型,只用于Json输出。e、 g.简化模型:

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 }
{ "productUri": "/product/456", "total": 789.01 }
请注意,大小写和属性重命名已更改

如果AccountID为null,则json必须如下呈现:

{ "accountUri": "/account/123", "productUri": "/product/456", "total": 789.01 }
{ "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}
三个问题:

  • 如何将“accountID”重命名为“accountUri”(将“productID”重命名为“productUri”)

  • 如何呈现这些属性的值(用包装器类的ToString()结果替换“{}”

  • 当一个属性为空时,我如何删除它

  • 谢谢

    编辑:尽管为每个模型编写一个转换器需要做大量工作,但它确实节省了编写两个映射器的时间。以下是我的概念验证测试:

        [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;
        }
    }
    
    额外的工作将是验证uri是否正确,而不仅仅是一个通用的“从uri末尾删除id”。

    1)使用JsonProperty属性的PropertyName参数,例如:

    [JsonProperty(PropertyName = "accountUri")]
    public AccountIdentifier AccountID { get; set; }
    
    2) {}是AccountIdentifier和ProductIdentifier类的内容。您需要为Order类编写自定义Json转换器,以便自定义(反)序列化内容

    这是相关的

    3) 您还可以通过JsonProperty进行设置

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    
    编辑:这里是Order类自定义JsonConvert的一部分。它不使用上述属性,而是手动处理。如果其他人能提供更好(更完整)的解决方案,请这样做

    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);
        }
    }
    

    既然您决定考虑使用适配器模式,这里有一个示例实现:测试:

    [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);
        }
    }
    

    如果您有任何问题,请发表评论,我将对任何需要进一步解释的内容进行注释。

    正确的解决方案是使用适配器模式。虽然这在技术上不是映射,但我怀疑您不会喜欢这样,因为您的声明“因此我不必为了json输出而将所有模型映射到另一个模型”。您还应该查看关注点分离。老实说,你真的应该重新考虑你对潜在解决方案的立场。你正在走一条没有多大意义的路,这条路很难走(而且会让其他开发人员感到困惑)。你应该已经使用完适配器模式,而不是等待答案。是的,我需要分离我的关注点。。。我将研究如何使用适配器模式。谢谢。我添加了一个答案来给你一个简明的例子。你的编辑是一个很好的答案。谢谢!编写所有覆盖需要做很多工作,但比第一个答案更容易接受,因为我不想使用属性。谢谢很好的解决方案。满足我的所有要求。。。esp没有属性。谢谢