【问题标题】:Convert JSON camel case to snake case (and vice versa) and stringify numeric values将 JSON 骆驼案例转换为蛇案例(反之亦然)并将数值字符串化
【发布时间】:2019-05-15 17:29:27
【问题描述】:

我必须向 Web REST 服务发送和接收 JSON 对象。这些对象由一个 DLL 生成,该 DLL 以大写驼峰形式(“PropertyName”)序列化属性名称,并且 Web 服务需要蛇形大小写(“property_name”)。 此外,DLL 将数值序列化为浮点数,但 REST API 需要所有字符串。 处理完对象后,REST 服务返回蛇案例 JSON。

JSON 很复杂,包含嵌套的数组和对象。从 REST 字符串转换回来时,我可以跳过数字字符串的反字符串化,但我仍然需要将属性名称重新转换为大写字母。

我正在考虑使用 Newtonsoft Json 库编写一个辅助类,但它看起来比我预期的要复杂。转换器应该接受 JSON 并返回 JSON。

例子:

{
    "FirstObject": {
        "NestedObject": {
            "AttributeString": "ok",
            "AttributeNumeric": 123.45
        },
        "OtherObject": [{
            "ArrayVal": 100
        }, {
            "ArrayVal": 200
        }]
    }
}

应该变成

{
    "first_object": {
        "nested_object": {
            "attribute_string": "ok",
            "attribute_numeric": "123.45"
        },
        "other_object": [{
            "array_val": "100"
        }, {
            "array_val": "200"
        }]
    }
}

我看到 Json.Net 库有 SnakeCaseNamingStrategyCamelCaseNamingStrategy 类,所以想法是使用 JsonTextReader 来解析输入,更改属性名称的命名约定,将数值设置为字符串,并使用JsonTextWriter 写入修改后的令牌。

我找不到任何有关如何执行此操作的示例。

【问题讨论】:

    标签: c# json json.net camelcasing


    【解决方案1】:

    最简单的方法是使用一组与您的 JSON 匹配的模型类。 (您可以通过将 JSON 的完整示例复制到剪贴板,然后使用 Edit -> Paste Special -> Paste JSON as Classes 函数在 Visual Studio 中生成类。)使模型类对属性名称使用大写驼峰式(这是C#),并使用字符串代替数字属性。

    因此,对于您的示例 JSON,模型类将如下所示:

    public class RootObject
    {
        public FirstObject FirstObject { get; set; }
    }
    
    public class FirstObject
    {
        public NestedObject NestedObject { get; set; }
        public List<OtherObject> OtherObject { get; set; }
    }
    
    public class NestedObject
    {
        public string AttributeString { get; set; }
        public string AttributeNumeric { get; set; }
    }
    
    public class OtherObject
    {
        public string ArrayVal { get; set; }  // using string instead of int here
    }
    

    然后,要将大写的驼峰式 JSON 转换为蛇形,您可以这样做:

    var obj = JsonConvert.DeserializeObject<RootObject>(json);
    var settings = new JsonSerializerSettings
    {
        ContractResolver = new DefaultContractResolver
        {
            NamingStrategy = new SnakeCaseNamingStrategy { ProcessDictionaryKeys = true }
        },
        Formatting = Formatting.Indented
    };
    json = JsonConvert.SerializeObject(obj, settings);
    

    原始 JSON 将自然反序列化为模型,因为属性名称已经匹配。 Json.Net 会根据需要自动将 JSON 中的数值转换为字符串以适应类属性。在序列化时,SnakeCaseNamingStrategy 开始发挥作用,将属性名称更改为蛇形大小写。数值被写成字符串,因为这是在类中声明属性的方式。

    要返回另一个方向,你会这样做:

    obj = JsonConvert.DeserializeObject<RootObject>(json, settings);  // same settings as above
    json = JsonConvert.SerializeObject(obj, Formatting.Indented);
    

    在这里,在反序列化期间,Json.Net 使用 SnakeCaseNamingStrategy 将模型属性名称再次转换为蛇形大小写,以将它们与 JSON 属性匹配。 JSON 中的数值已经是字符串,因此不需要转换。在序列化时,我们没有使用任何特殊设置,所以属性名称完全按照声明的方式写出,这是大写的驼峰式。保存数值的字符串属性仍然是字符串(您在问题中说这没问题)。

    这是一个往返演示:https://dotnetfiddle.net/3Pb1fT


    如果您没有可使用的模型,仍然可以使用您建议的 JsonReader/JsonWriter 方法进行此转换,但需要更多代码将它们粘合在一起并执行转变。这是一个辅助方法,可以完成大部分繁重的工作:

    public static void ConvertJson(TextReader textReader, TextWriter textWriter, 
                                   NamingStrategy strategy, 
                                   Formatting formatting = Formatting.Indented)
    {
        using (JsonReader reader = new JsonTextReader(textReader))
        using (JsonWriter writer = new JsonTextWriter(textWriter))
        {
            writer.Formatting = formatting;
            if (reader.TokenType == JsonToken.None)
            {
                reader.Read();
                ConvertJsonValue(reader, writer, strategy);
            }
        }
    }
    
    private static void ConvertJsonValue(JsonReader reader, JsonWriter writer, 
                                         NamingStrategy strategy)
    {
        if (reader.TokenType == JsonToken.StartObject)
        {
            writer.WriteStartObject();
            while (reader.Read() && reader.TokenType != JsonToken.EndObject)
            {
                string name = strategy.GetPropertyName((string)reader.Value, false);
                writer.WritePropertyName(name);
                reader.Read();
                ConvertJsonValue(reader, writer, strategy);
            }
            writer.WriteEndObject();
        }
        else if (reader.TokenType == JsonToken.StartArray)
        {
            writer.WriteStartArray();
            while (reader.Read() && reader.TokenType != JsonToken.EndArray)
            {
                ConvertJsonValue(reader, writer, strategy);
            }
            writer.WriteEndArray();
        }
        else if (reader.TokenType == JsonToken.Integer)
        {
            // convert integer values to string
            writer.WriteValue(Convert.ToString((long)reader.Value));
        }
        else if (reader.TokenType == JsonToken.Float)
        {
            // convert floating point values to string
            writer.WriteValue(Convert.ToString((double)reader.Value,
                              System.Globalization.CultureInfo.InvariantCulture));        
        }
        else // string, bool, date, etc.
        {
            writer.WriteValue(reader.Value);
        }
    }
    

    要使用它,您只需为输入 JSON 设置一个 TextReader,为输出设置一个 TextWriter,并传入您要用于转换的适当 NamingStrategy。例如,要将原始 JSON 字符串转换为蛇形大小写,您可以这样做:

    using (StringReader sr = new StringReader(upperCamelCaseJson))
    using (StringWriter sw = new StringWriter())
    {
        ConvertJson(sr, sw, new SnakeCaseNamingStrategy(), formatting);
        string snakeCaseJson = sw.ToString();
        ...
    }
    

    或者,如果您的 JSON 的源和/或目标是某种流,您可以改用 StreamReader/StreamWriter

    using (StreamReader sr = new StreamReader(inputStream))
    using (StreamWriter sw = new StreamWriter(outputStream))
    {
        ConvertJson(sr, sw, new SnakeCaseNamingStrategy(), formatting);
    }
    

    现在,对于回程,有一点问题。 NamingStrategy 只在一个方向起作用;它不提供反转转换的工具。这意味着 Newtonsoft 提供的 NamingStrategy 类都不能按照您想要的方式将蛇形大小写转换回大骆驼形大小写。 CamelCaseNamingStrategy 不起作用,因为它不希望以蛇形案例开始(它想要大骆驼案例),并且它的输出无论如何都不是大骆驼。 DefaultNamingStrategy 也不起作用,因为它实际上根本不进行任何转换——它只是一个传递。

    解决方案是制作您自己的自定义NamingStrategy。幸运的是,这并不难做到。只需从基类NamingStrategy 派生并实现抽象ResolvePropertyName 方法:

    // This naming strategy converts snake case names to upper camel case (a.k.a. proper case)
    public class ProperCaseFromSnakeCaseNamingStrategy : NamingStrategy
    {
        protected override string ResolvePropertyName(string name)
        {
            StringBuilder sb = new StringBuilder(name.Length);
            for (int i = 0; i < name.Length; i++)
            {
                char c = name[i];
    
                if (i == 0 || name[i - 1] == '_')
                    c = char.ToUpper(c);
    
                if (c != '_')
                    sb.Append(c);
            }
            return sb.ToString();
        }
    }
    

    现在您可以将这个新策略传递给上述ConvertJson 方法,以将蛇形 JSON 格式转换回大骆驼式大小写。

    往返演示:https://dotnetfiddle.net/jt0XKD

    【讨论】:

    • 谢谢布赖恩。这对我来说真的很有趣,因为我对在 .Net 中处理 json 对象还很陌生。我看到的唯一问题是这里的对象非常复杂(这是从明年 1 月开始在意大利强制使用的新电子发票)。大多数时候,数据只是完整类型定义的一小部分。我什至没有 json 中规范的完整原型(我在 xml 中有它)。因此产生了 JsonTextReader/Write 方法的想法。不管怎样,你给了我一个很好的暗示,我将在周末工作。
    • 再次感谢您,布赖恩。你让我省了很多麻烦。我两种方式都试过了,效果很好。我只需要在 ProperCaseFromSnakeCaseNamingStrategy 覆盖中创建一个字典来处理一些具有非常规大小写的标记(例如,“AliquotaIVA”蛇形化为“aliquota_iva”,然后重新命名为“AliquotaIva”)。
    • 有一点需要注意,以防有人需要对浮点数进行字符串化:如果您使用的是欧洲本地化系统,Brian 的代码将使用逗号作为小数分隔符。只需更改 ConvertJsonValue() 的这一部分即可轻松修复:else if (reader.TokenType == JsonToken.Float) { // convert floating point values to string writer.WriteValue(Convert.ToString((double)reader.Value, System.Globalization.CultureInfo.InvariantCulture)); }
    • 很好地掌握了十进制转换。我已将其编辑到上面的答案中。
    猜你喜欢
    • 2019-10-30
    • 1970-01-01
    • 1970-01-01
    • 2020-02-10
    • 1970-01-01
    • 2022-06-11
    • 1970-01-01
    • 2020-05-03
    • 2015-05-27
    相关资源
    最近更新 更多