我正在尝试调用一个外部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> { }
我该怎么做才能让所有的单元测试都是绿色的?
使用Json.NET.属性的标准JsonConverter
是不可能的
为此,您可以编写自己的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();
}
}
注意:这个类可能(我几乎可以肯定它确实包含(包含bug,所以你需要为一个有null的角落用例写更多的测试,等等
从技术上讲,这并不能回答您的确切问题,但我会使用其他方法来转换为请求和响应。
将序列化规则修改为不同的请求和响应将需要更多的工作,对我来说,这违反了最不令人惊讶的原则。这样,任何审查Message
类代码的工程师都会立即了解发生了什么
class Message
{
public string MessageText { get; set; }
public Attachment[] Attachments { get; set; }
public string ToRequest()
{
return JsonConvert.SerializeObject( new { MessageText = this.MessageText, Attachments = new { Attachment = this.Attachments }});
}
static public Message FromResponse(string input)
{
return JsonConvert.DeserializeObject<Message>(input);
}
}
要反序列化:
var actual = Message.FromResponse(json);
要序列化:
var json = actual.ToRequest();