簡體   English   中英

如何將Badgerfish樣式的JSON轉換為.NET對象或C#中的XML?

[英]How to convert Badgerfish style JSON to a .NET object or XML in C#?

使用REST API時,.NET更喜歡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響應(從原始規范稍作修改)?

我也負責使用D&B的API,並在檢查是否存在針對.NET中的BadgerFish的現有解決方案時遇到了這個問題。

像你一樣,我只需要擔心反模型化到我的.NET模型中。

此外,在閱讀了D&B的BadgerFish版本之后,我沒有看到任何需要特別考慮它們。 以下代碼似乎處理D&B的格式就好了。

為什么BadgerFish?

似乎D&B已經有了很長一段時間的XML API,而不是序列化為XML JSON,他們決定通過將現有的XML直接轉換為JSON來生成他們的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();
    }
}

如果我想花更多的時間在這上面,它肯定會寫得更高效,但我根本不需要我的項目速度。

它目前正在利用JObject.Load(reader)ReadJson方法,它使用默認實現將JSON轉換為JObject

然后,我遞歸該對象的圖形,收集我想要重命名的屬性的路徑。 這是因為我無法在枚舉期間重命名它們,因為這會修改正在迭代的集合,這是出於明顯原因而不允許的。

收集路徑后,我迭代路徑,重命名這些特定屬性。 此過程首先刪除舊屬性,然后添加一個具有新名稱的新屬性。

對於那些如此傾向的人來說,更精明和高效的實現將在構建JObjectJsonReader的反序列化階段完成所有這些,在讀取器讀取屬性時重命名屬性。

用法

用法很簡單,如下:

var jsonSettings = new JsonSerializerSettings();
jsonSettings.Converters.Add(new BadgerFishJsonConverter());

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

鑒於以下兩種模式:

public class SalesRevenueAmount
{
    public string CurrencyISOAlpha3Code { get; set; }
    public string Value { get; set; }
}

public class SalesRevenueAmounts
{
    public IEnumerable<SalesRevenueAmount> SalesRevenueAmount { get; set; }
}

其他參考文獻

作為我的解決方案的一部分,我使用了用戶Brian Rogers的 這個Rename擴展 ,我發現它有助於整理我的代碼。 我添加了通過簡單地將參數更改為Func<JProperty, string>來傳遞名稱提供程序函數的Func<JProperty, string>以便我可以控制提供程序名稱的創建方式。

全面實施,如下:

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);
    }
}

希望這有助於在將來節省一些時間。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM