Проблема с сериализацией JSON в CamelCase в C#

Я пытаюсь сериализовать строку JSON, чтобы преобразовать имена свойств в CamelCase, используя библиотеку System.Text.Json. Тем не менее, я все еще получаю результат с помощью PascalCase.

Вот мой код:

var stringJson = "{ \"Key\": \"TextA\" }";

var outcome = SerializeWithCamelCase(stringJson);

private static JsonElement SerializeWithCamelCase(string jsonContent)
{
    var options = new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        PropertyNameCaseInsensitive = true
    };

    using var document = JsonDocument.Parse(jsonContent);
    var element = document.RootElement;

    var jsonString = JsonSerializer.Serialize(element, options);

    using var camelCaseDocument = JsonDocument.Parse(jsonString);
    return camelCaseDocument.RootElement.Clone();
}

Я пробовал много вещей, но с PascalCase все равно получаю результат.

Я ожидаю, что выходные данные будут иметь имена свойств в CamelCase, но я все равно получаю PascalCase.

Ожидаемый результат:

{
    "key": "TextA"
}

Фактический результат:

{
    "Key": "TextA"
}

Последний тип должен быть JsonElement.

Никаких репродукций с реальным занятием. Однако код вопроса не пытается сериализовать объект с использованием верблюжьего регистра, он пытается преобразовать строку JSON, использующую регистр Pascal, в верблюжий регистр. JsonDocument.Parse однако не создает объект, а создает документ JSON с уже указанными ключами. При сериализации будет сериализовано содержимое JsonDocument, а не его свойства, поэтому политика именования не применяется.
Panagiotis Kanavos 15.07.2024 19:01

Проверьте дубликат. Невозможно изменить сгенерированный JsonDocument. Если вы десериализовали в правильный класс, политика именования будет работать. Как показывает дубликат, вы можете использовать собственный преобразователь, чтобы принудительно использовать политику. Или вы можете десериализовать в JsonNode вместо JsonDocument и переименовать ключи.

Panagiotis Kanavos 15.07.2024 19:26

Если вы решите изменить ключи, вы можете использовать JsonNamingPolicy.CamelCase.ConvertName(someString)

Panagiotis Kanavos 15.07.2024 19:28

нет определенной структуры json. Там может быть что угодно.

Krystian 15.07.2024 19:36

В этом случае используйте собственный конвертер в дублирующемся вопросе. Если бы строка JSON была простой, вы могли бы десериализовать ее, например, в JsonObject и начать заменять атрибуты их эквивалентами в верблюжьем регистре. Однако при произвольной структуре вам придется обрабатывать вложенные объекты и массивы объектов на неизвестную глубину. Для этого требуется как минимум рекурсия и гораздо больше кода, чем в пользовательском конвертере.

Panagiotis Kanavos 15.07.2024 19:39

Кстати, ответ, который вы приняли, - худшая из возможных идей. Нет смысла что-либо клонировать, а тем более дважды анализировать каждый элемент. А многочисленные операции по манипулированию строками приведут к созданию большого количества временных строк. Даже ToCamel неверно, и JsonNamingPolicy.CamelCase.ConvertName(someString) уже правильно его реализовал.

Panagiotis Kanavos 15.07.2024 19:41
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
55
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Используйте следующий подход:

using System;
using System.Collections.Generic;
using System.Text.Json;

class Program
{
    static void Main()
    {
        var stringJson = "{ \"Key\": \"TextA\" }";
        var outcome = SerializeWithCamelCase(stringJson);
        Console.WriteLine(outcome);
    }

    private static string SerializeWithCamelCase(string jsonContent)
    {
        using var document = JsonDocument.Parse(jsonContent);
        var root = document.RootElement;
        var modifiedObject = ConvertPropertyNamesToCamelCase(root);
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            WriteIndented = true
        };

        return JsonSerializer.Serialize(modifiedObject, options);
    }

    private static JsonElement ConvertPropertyNamesToCamelCase(JsonElement element)
    {
        switch (element.ValueKind)
        {
            case JsonValueKind.Object:
                var objBuilder = new Dictionary<string, JsonElement>();
                foreach (var property in element.EnumerateObject())
                {
                    var camelCaseName = ToCamelCase(property.Name);
                    var camelCaseValue = ConvertPropertyNamesToCamelCase(property.Value);
                    objBuilder[camelCaseName] = camelCaseValue;
                }
                return JsonDocument.Parse(JsonSerializer.Serialize(objBuilder)).RootElement;

            case JsonValueKind.Array:
                var arrayBuilder = new List<JsonElement>();
                foreach (var item in element.EnumerateArray())
                {
                    var camelCaseItem = ConvertPropertyNamesToCamelCase(item);
                    arrayBuilder.Add(camelCaseItem);
                }
                return JsonDocument.Parse(JsonSerializer.Serialize(arrayBuilder)).RootElement;

            default:
                return element.Clone();
        }
    }

    private static string ToCamelCase(string name)
    {
        if (string.IsNullOrEmpty(name))
            return name;

        if (name.Length == 1)
            return name.ToLowerInvariant();

        return char.ToLowerInvariant(name[0]) + name.Substring(1);
    }
}

Приведенный выше подход даст вам результат ниже:

{
  "key": "TextA"
}

См. рабочий .netfiddle здесь

На самом деле вопрос не в том, как сериализовать с использованием Camel-Case, а в том, как преобразовать документ с использованием Pascal-Case в Camel-Case.

Проблема вопроса в том, что JsonDocument.Parse создает JsonDocument, который представляет фактическое содержимое, а не атрибуты, требующие сериализации. Невозможно изменить способ обработки JsonDocument синтаксического анализа или десериализации. JsonDocument доступен только для чтения, поэтому его невозможно изменить.

Для конвертации из Pascal-case в Camel есть два варианта:

  • Используйте собственный преобразователь, который использует политику или
  • Десериализовать в изменяемый JsonNode и явно изменить ключи.

Используйте специальный преобразователь типов

Один из вариантов, показанный в этом связанном вопросе, — использовать собственный преобразователь, который фактически использует политику именования. Обратите внимание, что конвертер обрабатывает каждый тип элемента явно:

public class JsonElementConverter : JsonConverter<JsonElement>
{
    public override void Write(Utf8JsonWriter writer, JsonElement value, JsonSerializerOptions options)
    {
        switch (value.ValueKind)
        {
            case JsonValueKind.Object:
                var policy = options.PropertyNamingPolicy;
                writer.WriteStartObject();
                foreach (var pair in value.EnumerateObject())
                {
                    writer.WritePropertyName(policy?.ConvertName(pair.Name) ?? pair.Name);
                    Write(writer, pair.Value, options);
                }
                writer.WriteEndObject();
                break;
            case JsonValueKind.Array:
                writer.WriteStartArray();
                foreach (var item in value.EnumerateArray())
                    Write(writer, item, options);
                writer.WriteEndArray();
                break;
            default:
                value.WriteTo(writer);
                break;
        }
    }
    
    public override JsonElement Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        using var doc = JsonDocument.ParseValue(ref reader);
        return doc.RootElement.Clone();
    }
}

При этом политика именования работает:

using var doc = JsonDocument.Parse(jsonString);

JsonNamingPolicy policy = JsonNamingPolicy.CamelCase;
var options = new JsonSerializerOptions
{
    Converters = { new JsonElementConverter() },
    PropertyNamingPolicy = policy,
    WriteIndented = true // Or false, if you prefer
};

var serialized = JsonSerializer.Serialize(doc.RootElement, options);

Использование JsonNode

Другой вариант, упомянутый в соответствующем вопросе, но не реализованный, — это десериализация в JsonNode и изменение ключей. Опять же, каждый тип элемента должен быть обработан. Однако на этот раз рекурсия необходима для обработки всех вложенных элементов.

Несмотря на это, функция Camelize не такая уж и сложная:

public static JsonNode Camelize(JsonNode node,JsonNamingPolicy policy)
{
    if (node is JsonObject obj)
    {
        var pairs=obj.ToList();     
        foreach(var pair in pairs)
        {
            obj.Remove(pair.Key);
            obj[policy.ConvertName(pair.Key)]=Camelize(pair.Value,policy);
        }           
        return obj;         
    }
    if (node is JsonArray arr)
    {
        foreach(var item in arr)
        {
            Camelize(item,policy);
        }
        
        return arr;
    }
    return node;
}

Это может камелизировать сложный объект:

    var json = """
    {"Key":"TextA",
     "Key2":{"Apples":14},
     "Items":[{"Bazinga":"X"},
              {"Bananas":{
                  "Potatoes":3,
                  "Moo":[1,2,3]}
              }
             ]}
    """;        
    var obj=JsonSerializer.Deserialize<JsonNode>(json);         
    
    var x=Camelize(obj,JsonNamingPolicy.CamelCase);

    var options=new JsonSerializerOptions{
        WriteIndented=true
    } ;
    
    Console.WriteLine( JsonSerializer.Serialize(x,options));

Результат

{
  "key": "TextA",
  "key2": {
    "apples": 14
  },
  "items": [
    {
      "bazinga": "X"
    },
    {
      "bananas": {
        "potatoes": 3,
        "moo": [
          1,
          2,
          3
        ]
      }
    }
  ]
}

Другие вопросы по теме