c# - 如何在 C# 中映射支持 2 种反序列化方式的集合?
问题描述
我正在尝试调用外部 WebAPI,并且输入的形状与输出的形状相似。我想为输入和输出重用相同类型的对象。
事实上,有一个简单的方法可以让请求和响应模型像下面这样在Attachments
属性中添加额外的层:
request = new { MessageText = "text", Attachments = new {Attachment = attachments }};
response = new { MessageText = "text", Attachments = attachments };
但是我很感兴趣这两个模型是否可以由单个类表示,但与反序列化相比,序列化为不同的模式。理想情况下寻找属性的一些属性,但对自定义转换器或其他任何东西开放。
细节:
WebAPI 的输入 json 必须是这样的:
{
"MessageText": "text",
"Attachments": {
"Attachment": [
{
"FileName": "file1"
},
{
"FileName": "file2"
}
]
}
}
但是当 WebAPI 在结果中返回相同的对象时,它会返回:
{
"MessageText": "text",
"Attachments": [
{
"FileName": "file1"
},
{
"FileName": "file2"
}
]
}
我不知道为什么 WebAPI 在输入中需要一个级别“附件”,并且不会在输出中返回......当然,我对 WebAPI 的代码没有任何控制权。
如何使用 C# 映射我的 DTO 类?他们必须通过这 3 个单元测试?
[TestClass]
public class UnitTestJsonCollectionSerialisation
{
private void TestMethodDeSerialization(string json)
{
var actual = JsonConvert.DeserializeObject<Message>(json);
Assert.AreEqual(2, actual.Attachments.Count);
Assert.AreEqual("file1", actual.Attachments[0].FileName);
}
[TestMethod]
public void TestMethodDeSerialization1()
{
const string JSON1 = "{\"MessageText\":\"text\",\"Attachments\":[{\"FileName\":\"file1\"},{\"FileName\":\"file2\"}]}";
TestMethodDeSerialization(JSON1);
}
[TestMethod]
public void TestMethodDeSerialization2()
{
const string JSON2 = "{\"MessageText\":\"text\",\"Attachments\":{\"Attachment\":[{\"FileName\":\"file1\"},{\"FileName\":\"file2\"}]}}";
TestMethodDeSerialization(JSON2);
}
[TestMethod]
public void TestMethodSerialization()
{
Message message = new Message();
message.MessageText = "text";
message.Attachments.Add(new Attachment() { FileName = "file1" });
message.Attachments.Add(new Attachment() { FileName = "file2" });
string actual = JsonConvert.SerializeObject(message);
Assert.AreEqual("{\"MessageText\":\"text\",\"Attachments\":{\"Attachment\":[{\"FileName\":\"file1\"},{\"FileName\":\"file2\"}]}}", actual);
}
}
以下 DTO 类通过了第一个单元测试,但最后一个没有通过。
public class Message
{
public string MessageText { get; set; }
public Attachments Attachments { get; set; } = new Attachments();
}
public class Attachment
{
public string FileName { get; set; }
}
public class Attachments : List<Attachment> { }
我该怎么做才能让所有单元测试都变成绿色?
解决方案
JsonConverter
使用带有 Json.NET 属性的标准是不可能的。
您可以为此编写自己JsonConverter<Message>
的,但最好同时拥有请求和响应模型,因为它们可以相互独立地更改,因此您需要编写更复杂的转换器。
MessageJsonConverter
将通过您的测试的样本
public class MessageJsonConverter : JsonConverter<Message>
{
public override Message ReadJson(JsonReader reader, System.Type objectType, [AllowNull] Message existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var message = existingValue ?? new Message();
while (reader.Read() && reader.TokenType != JsonToken.EndObject)
{
var propertyName = (string) reader.Value;
if (propertyName.Equals(nameof(message.MessageText), StringComparison.OrdinalIgnoreCase))
{
reader.Read();
message.MessageText = (string) reader.Value;
}
else if (propertyName.Equals(nameof(message.Attachments), StringComparison.OrdinalIgnoreCase))
{
reader.Read();
if (reader.TokenType == JsonToken.StartArray)
{
message.Attachments = serializer.Deserialize<Attachments>(reader);
}
else if (reader.TokenType == JsonToken.StartObject)
{
reader.Read();
var subPropertyName = (string) reader.Value;
if (subPropertyName.Equals("Attachment", StringComparison.OrdinalIgnoreCase))
{
reader.Read(); // now JsonToken.StartArray
message.Attachments = serializer.Deserialize<Attachments>(reader); // now JsonToken.EndArray
reader.Read(); // now JsonToken.PropertyName or JsonToken.EndObject
if (reader.TokenType != JsonToken.EndObject)
throw new JsonSerializationException($"Additional properties in [{propertyName}]");
}
else
{
throw new JsonSerializationException($"Unknown property [{propertyName}.{subPropertyName}]");
}
}
}
else
{
throw new JsonSerializationException($"Unknown property [{propertyName}]");
}
}
return message;
}
public override void WriteJson(JsonWriter writer, [AllowNull] Message value, JsonSerializer serializer)
{
if (value is null)
{
writer.WriteNull();
return;
}
writer.WriteStartObject();
{
writer.WritePropertyName(nameof(Message.MessageText));
writer.WriteValue(value.MessageText);
writer.WritePropertyName(nameof(Message.Attachments));
writer.WriteStartObject();
{
writer.WritePropertyName("Attachment");
serializer.Serialize(writer, value.Attachments);
}
writer.WriteEndObject();
}
writer.WriteEndObject();
}
}
注意:这个类可以(我几乎可以肯定它确实)包含错误,因此您需要为带有空值等的极端情况编写更多测试。
推荐阅读
- c# - 使用MediaCapture c#在触发前后记录秒数的圆形缓冲区
- javascript - 在 VueJS 项目中安装 slick carousel
- javascript - 流式传输 html5 画布内容的有效方式?
- docker - 即使在配置 DOCKER_OPTS="iptables=false" 之后,ubuntu 18.04 服务器 ufw 也不会阻塞
- java - 在java中导入特定的类
- azure - 在 Azure 中测试机器人应用程序没有响应
- node.js - 如何使用 express nodejs 和 mongoose 在 Restful API 中执行 PUT 和 DELETE 操作?
- wpf - WPF双动画延迟
- session - OFBiz 会话过期:FO PDF 下载后
- url - Crystal-lang:如何在重定向后找到结束 URL?