Преобразовать случай верблюда JSON в случай змеи (и наоборот) и преобразовать в числовые значения - PullRequest
0 голосов
/ 14 декабря 2018

Я должен отправлять и получать объекты JSON в веб-REST-сервис.Объекты создаются библиотекой DLL, которая сериализует имена атрибутов в верхнем регистре верблюда («PropertyName»), а веб-сервису требуется регистр «змея» («имя_свойства»).Плюс, DLL сериализует числовые значения как числа с плавающей запятой, но API REST хотят все строки.После обработки объекта служба 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 имеет SnakeCaseNamingStrategy иCamelCaseNamingStrategy классов, поэтому идея заключалась в использовании JsonTextReader для анализа входных данных, изменения соглашения об именах имен свойств, установки числовых значений в виде строки и записи измененных токенов с использованием JsonTextWriter.

Я не смог найти образец того, как это сделать.

1 Ответ

0 голосов
/ 14 декабря 2018

Самый простой способ сделать то, что вы хотите, это использовать набор классов моделей, которые соответствуют вашему JSON.(Вы можете создать классы в Visual Studio, скопировав полный образец JSON в буфер обмена и затем используя функцию Edit -> Paste Special -> Paste JSON as Classes.) Заставьте классы модели использовать верхний регистр верблюда для имен свойств (что является стандартным соглашением об именах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);
    }
}

Чтобы использовать его, вам просто нужно установить TextReader для вашего входного JSON и TextWriter для выходногои передайте соответствующий NamingStrategy, который вы хотите использовать для преобразования.Например, чтобы преобразовать исходную строку JSON в случай змеи, вы должны сделать следующее:

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

Или, если источником и / или местом назначения для вашего JSON является какой-либо поток, вы можете использоватьStreamReader / StreamWriter вместо:

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

Теперь для обратной поездки есть небольшая проблема.A NamingStrategy работает только в одном направлении;он не предоставляет возможности для обращения конверсии.Это означает, что ни один из NamingStrategy классов, предоставленных Newtonsoft, не будет работать для преобразования змеиного случая обратно в верхний верблюжий случай, как вы хотите.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-случай обратнок верхнему верблюжьему делу

...