首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何将Badgerfish样式的JSON转换为.NET对象或C#中的XML?

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

Stack Overflow用户
提问于 2016-08-23 20:36:36
回答 1查看 710关注 0票数 3

.NET在使用休息 API时更喜欢Newtonsoft JSON序列化程序/反序列化器。

D&B Direct实现使用BadgerFish方法(主要存在于Java (jettison命名空间)中)用于JSON,但有一些细微的变化:D&B BadgerFish

我想将D&B BadgerFish JSON响应映射到.NET类。有一个GitHub项目https://github.com/bramstein/xsltjson/,它支持从XML到JSON的转换(支持BadgerFish),但是如何做相反的事情,如下所述:

XSLTJSON支持几种不同的JSON输出格式,从紧凑的输出格式到支持BadgerFish约定的JSON输出格式,这允许XML和JSON之间的往返。

例如,假设D&B后端REST服务正在转换此XML:

代码语言:javascript
复制
<SalesRevenueAmount CurrencyISOAlpha3Code="USD”>1000000</SalesRevenueAmount>
<SalesRevenueAmount CurrencyISOAlpha3Code="CAD”>1040000</SalesRevenueAmount>

。。转入:

代码语言:javascript
复制
"SalesRevenueAmount": [     {
   "@CurrencyISOAlpha3Code": "USD",
   "$": 1000000
},
{
   "@CurrencyISOAlpha3Code": "CAD",
   "$": 1040000
}
]

那么,如何在BadgerFish REST客户机中使用返回的JSON格式的JSON响应(与原始规范相比略有修改)?

EN

回答 1

Stack Overflow用户

发布于 2019-04-12 21:04:31

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

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

此外,在阅读了D&B的BadgerFish变体之后,我认为没有必要对它们进行具体解释。下面的代码似乎很好地处理了D&B格式。

为什么是BadgerFish?

似乎D&B已经使用XML很长一段时间了,他们决定通过将现有的直接转换为JSON来生成JSON内容类型,而不是序列化为XML或 JSON。

这导致需要解决XML和JSON结构之间的不一致性。在XML中,可以有属性和与单个元素相关联的值。这种模式在JSON中并不存在。JSON只是键/值。

因此,BadgerFish是解决这两种数据格式之间不一致的标准。当然,它可以用其他方式解决,这只是许多想法中的一个。

目标

为了解决这个问题,我首先需要弄清楚的是我的预期结果是什么。

使用您的示例,我决定使用以下JSON:

代码语言:javascript
复制
"SalesRevenueAmount": [
    {
       "@CurrencyISOAlpha3Code": "USD",
       "$": 1000000
    },
    {
       "@CurrencyISOAlpha3Code": "CAD",
       "$": 1040000
    }
]

应按以下方式将其反序列化为模型集合:

代码语言:javascript
复制
public class SalesRevenueAmount {
    public string CurrencyISOAlpha3Code { get; set; }
    public string Value { get; set; }
}

最简单的解决方案

最简单的解决方案,也是最明显的,就是将JsonProperty属性附加到我希望具有此@$命名约定的每个属性中。

代码语言:javascript
复制
public class SalesRevenueAmount {
    [JsonProperty("@CurrencyISOAlpha3Code")]
    public string CurrencyISOAlpha3Code { get; set; }
    [JsonProperty("$")]
    public string Value { get; set; }
}

这是相对简单的做法,但也极容易出错。如果可以避免,我也不喜欢在我的模型中附加基础设施层的特定属性。

更好的解决方案

因此,我推测更好的解决方案将不会强迫我维护和手动编写这些很容易出错的注释。当然,我仍然需要编写属性名称本身,但是可以很容易地在Visual或任何您喜欢的IDE中重构这些名称。另一方面,除非运行时或单元测试失败,否则属性中的神奇字符串才会被捕获。

因此,我想要一些更自动,更健壮,更干燥的东西。在深入研究Newtonsoft JSON之后,我终于想出了一个我满意的解决方案。我创建了一个简单的JsonConverter,我称之为BadgerFishJsonConverter

当前的实现只处理反序列化,但要使其适应序列化并不太困难。我只是还没有必要。如果我将来这样做,我会回来更新我的答案。

代码语言:javascript
复制
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方法是JObject.Load(reader),它使用默认实现将JSON转换为JObject

然后,对该对象的图进行递归,收集要重命名的属性的路径。这是因为我不能在枚举期间重命名它们,因为这会修改正在迭代的集合,这显然是不允许的。

收集路径后,我迭代路径,重命名这些特定属性。此过程首先删除旧属性,然后添加具有新名称的新属性。

对于那些倾向于此的人来说,一个更明智和更有效的实现将在JsonReader反序列化阶段完成所有这些工作--构建JObject,在从读取器读取属性时重命名这些属性。

用法

用法简单,如下所示:

代码语言:javascript
复制
var jsonSettings = new JsonSerializerSettings();
jsonSettings.Converters.Add(new BadgerFishJsonConverter());

var obj = JsonConvert.DeserializeObject<SalesRevenueAmounts>(json, jsonSettings); 

鉴于以下两种模式:

代码语言:javascript
复制
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>,以便控制提供者名称的创建方式。

充分执行如下:

代码语言:javascript
复制
public static class Extensions
{
    public static void Rename(this JToken token, string newName)
    {
        token.Rename(prop => newName);
    }

    public static void Rename(this JToken token, Func<JProperty, string> nameProvider)
    {
        if (token == null)
            throw new ArgumentNullException("token", "Cannot rename a null token");

        JProperty property;

        if (token.Type == JTokenType.Property)
        {
            if (token.Parent == null)
                throw new InvalidOperationException("Cannot rename a property with no parent");

            property = (JProperty)token;
        }
        else
        {
            if (token.Parent == null || token.Parent.Type != JTokenType.Property)
                throw new InvalidOperationException("This token's parent is not a JProperty; cannot rename");

            property = (JProperty)token.Parent;
        }

        var newName = nameProvider.Invoke(property);
        var newProperty = new JProperty(newName, property.Value);
        property.Replace(newProperty);
    }
}

希望这能为将来的人节省时间。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/39110233

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档