如何将Badgerfish样式JSON转换为C#中的.NET对象或XML?

时间:2016-08-23 20:36:36

标签: c# .net json xml badgerfish

.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响应(稍微修改一下原始规范)?

1 个答案:

答案 0 :(得分:1)

我也承担了使用D&B API的任务,并且在检查以查看.NET中BadgerFish是否存在现有解决方案时遇到了这个问题。

像您一样,我只需要担心反序列化到我的.NET模型中。

此外,在阅读了D&B的BadgerFish版本之后,我认为没有必要专门考虑它们。以下代码似乎可以很好地处理D&B的格式。

为什么要使用BadgerFish?

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 extensionBrian 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>

希望这可以节省将来的时间。