.NET在使用REST API时更喜欢Newtonsoft JSON序列化器/反序列化器。
D& B Direct REST实现使用BadgerFish方法(主要存在于Java世界(jettison命名空间)中用于JSON,但有一些小的变化:D&B BadgerFish。
我想将D& B BadgerFish JSON响应映射到.NET类。 有一个GitHub项目https://github.com/bramstein/xsltjson/可以实现从XML到JSON的转换(支持BadgerFish),但我如何做到如下所述:
XSLTJSON支持多种不同的JSON输出格式,从紧凑的输出格式到支持BadgerFish约定,允许XML和JSON之间的往返。
例如,假设D& B后端REST服务正在转换此XML:
<SalesRevenueAmount CurrencyISOAlpha3Code="USD”>1000000</SalesRevenueAmount>
<SalesRevenueAmount CurrencyISOAlpha3Code="CAD”>1040000</SalesRevenueAmount>
..进入:
"SalesRevenueAmount": [ {
"@CurrencyISOAlpha3Code": "USD",
"$": 1000000
},
{
"@CurrencyISOAlpha3Code": "CAD",
"$": 1040000
}
]
那么如何在.NET REST客户端中使用这个返回BadgerFish格式的JSON响应(稍微修改一下原始规范)?
答案 0 :(得分:1)
我也承担了使用D&B API的任务,并且在检查以查看.NET中BadgerFish是否存在现有解决方案时遇到了这个问题。
像您一样,我只需要担心反序列化到我的.NET模型中。
此外,在阅读了D&B的BadgerFish版本之后,我认为没有必要专门考虑它们。以下代码似乎可以很好地处理D&B的格式。
D&B似乎已经拥有XML API已有一段时间了,他们决定通过转换以下内容来生成JSON内容类型,而不是序列化为XML 或 JSON。将其现有的XML直接转换为JSON。
这导致需要解决XML和JSON结构之间的不一致问题。在XML中,您可以具有与单个元素关联的属性和值。该范式在JSON中不存在。 JSON就是键/值。
因此,BadgerFish是一个标准,用于解决两种数据格式之间的这种不一致问题。当然,可以用其他方法解决它,这只是其中的许多想法之一。
要解决此问题,我需要弄清的第一件事是预期的结果。
使用您的示例,我确定使用以下JSON:
"SalesRevenueAmount": [
{
"@CurrencyISOAlpha3Code": "USD",
"$": 1000000
},
{
"@CurrencyISOAlpha3Code": "CAD",
"$": 1040000
}
]
应反序列化为以下模型的集合:
public class SalesRevenueAmount {
public string CurrencyISOAlpha3Code { get; set; }
public string Value { get; set; }
}
最简单,最明显的解决方案是将JsonProperty
属性附加到我希望具有此@
或$
命名约定的每个属性上。
public class SalesRevenueAmount {
[JsonProperty("@CurrencyISOAlpha3Code")]
public string CurrencyISOAlpha3Code { get; set; }
[JsonProperty("$")]
public string Value { get; set; }
}
这是相对简单的操作,但也极易出错。如果可以避免的话,我也不喜欢将基础结构层的特定属性附加到我的模型上。
因此,我推测 更好的解决方案 将是我不被迫维护和手写这些容易出错的注释的方法。当然,我仍然必须编写属性名称本身,但是可以在Visual Studio或您喜欢的任何IDE中轻松重构它们。另一方面,只有在运行时或单元测试失败后才能捕获属性中的魔术字符串。
因此,我想要一些更自动化,更强大且更干燥的东西。在研究了Newtonsoft JSON之后,我终于想出了一个令我满意的解决方案。我创建了一个简单的JsonConverter
,称为BadgerFishJsonConverter
。
当前的实现仅处理反序列化,但使其适应串行化并不难。我只是没有需要。如果将来会这样做,我会回来更新我的答案。
public class BadgerFishJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var source = JObject.Load(reader);
//Since we can't modify the internal collections, first we will get all the paths.
//Then we will proceed to rename them.
var paths = new List<string>();
collectPaths(source, paths);
renameProperties(source, paths);
return source.ToObject(objectType);
}
private void collectPaths(JToken token, ICollection<string> collection)
{
switch (token.Type)
{
case JTokenType.Object:
case JTokenType.Array:
foreach (var child in token)
{
collectPaths(child, collection);
}
break;
case JTokenType.Property:
var property = (JProperty)token;
if (shouldRenameProperty(property.Name))
{
collection.Add(property.Path);
}
foreach (var child in property)
{
collectPaths(child, collection);
}
break;
default:
break;
}
}
private void renameProperties(JObject source, ICollection<string> paths)
{
foreach (var path in paths)
{
var token = source.SelectToken(path);
token.Rename(prop => transformPropertyName(prop));
}
}
private bool shouldRenameProperty(string propertyName)
{
return propertyName.StartsWith("@") || propertyName.Equals("$");
}
private static string transformPropertyName(JProperty property)
{
if (property.Name.StartsWith("@"))
{
return property.Name.Substring(1);
}
else if (property.Name.Equals("$"))
{
return "Value";
}
else
{
return property.Name;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
如果我想在此上花费更多的时间,肯定会写出更高的性能,但我的项目根本不需要那种速度。
当前使用ReadJson
的{{1}}方法像使用默认实现一样将JSON转换为JObject.Load(reader)
。
然后,我递归遍历该对象的图,收集要重命名的属性的路径。这是因为我无法在枚举期间重命名它们,因为那样会修改正在迭代的集合,出于明显的原因,不允许这样做。
收集路径后,我将迭代路径,重命名那些特定属性。此过程首先删除旧属性,然后添加具有新名称的新属性。
对于那些倾向于的人,在JObject
的反序列化阶段建立JsonReader
的反序列化阶段,将采用更精明和有效的实现来完成所有这些工作,并重命名从读者那里读取的属性。 / p>
用法很简单,如下所示:
JObject
给出以下两个模型:
var jsonSettings = new JsonSerializerSettings();
jsonSettings.Converters.Add(new BadgerFishJsonConverter());
var obj = JsonConvert.DeserializeObject<SalesRevenueAmounts>(json, jsonSettings);
作为解决方案的一部分,我利用了来自用户this Rename extension的Brian Rogers,发现该代码有助于整理代码。我添加了通过简单地将参数更改为public class SalesRevenueAmount
{
public string CurrencyISOAlpha3Code { get; set; }
public string Value { get; set; }
}
public class SalesRevenueAmounts
{
public IEnumerable<SalesRevenueAmount> SalesRevenueAmount { get; set; }
}
来传递名称提供程序功能的功能,以便我可以控制如何创建提供程序名称。
完整实施,如下:
Func<JProperty, string>
希望这可以节省将来的时间。