2

I am trying to build a JsonElement (or similar) using a camelCase convention from a PascalCase string using System.Text.Json. Is there a way to enforce that behavior?

var jsonString = "{\"Property1\":\"s\", \"PropertyCamel\":{\"PropertyNested\":\"g\"}, \"PPP\":[{\"NestedList\":\"1\"}]}";
var deserialized = System.Text.Json.JsonSerializer.Deserialize<JsonElement>(jsonString,
    new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
        PropertyNameCaseInsensitive = true
    });
var serialized = System.Text.Json.JsonSerializer.Serialize(
    deserialized,
    new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
        PropertyNameCaseInsensitive = true
    });
// deserialized outputs property names in PascalCase

I have also tried deserializing -> serializing -> deserializing but without success.

Is there a way of doing so?

dbc
  • 104,963
  • 20
  • 228
  • 340
Vitor Durante
  • 950
  • 8
  • 25
  • Set `PropertyNameCaseInsensitive` to `true` and serialize again. – Beltway Nov 24 '21 at 12:52
  • 1
    @Beltway it doesn't work either. I believe since it is a JsonElement object it doesn't run the code on `JsonCamelCaseNamingPolicy` – Vitor Durante Nov 24 '21 at 13:38
  • Can't you map it to a transient object? By default `System.Text.Json` should use camelCase when serializing from shorthand getters written in PascalCase. – Beltway Nov 24 '21 at 14:02

2 Answers2

1

There is no way to load or deserialize a JsonDocument or JsonElement and convert it to camelCase during the loading process. JsonDocument and JsonElement are just read-only, structured views of the utf-8 JSON byte sequence from which they were loaded and as such there's no way to transform the view during (or after) the parsing process. For confirmation, JsonDocument, JsonElement and Utf8JsonReader are all sealed and JsonElement has no publicly accessible constructor so there simply isn't an extension point to inject custom transformation of property names.

As an alternative, you could camel-case the property names during re-serialization by creating the following custom JsonConverter<JsonElement>:

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

And then re-serializing your JsonDocument.RootElement using JsonSerializer like so:

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

Notes:

  • Be sure to serialize the JsonDocument.RootElement rather than the JsonDocument. If you serialize the document directly the converter is never invoked.

  • In .NET 6 it should be possible to transform the property names during deserialization/loading using a custom converter by deserializing to the new JsonNode DOM, whose elements can be constructed and modified directly by applications code.

  • As an alternative, if you don't require use of JsonElement, you could consider deserializing to an ExpandoObject or similar and remapping the properties during deserialization by extending ObjectAsPrimitiveConverter from this answer to C# - Deserializing nested json to nested Dictionary<string, object> to do the necessary name transformation.

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
-1

Referring to this answer, using Newtonsoft.Json

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Dynamic;
    
var jsonString = "{\"Property1\":\"s\", \"PropertyCamel\":{\"PropertyNested\":\"g\"}, \"PPP\":[{\"NestedList\":\"1\"}]}";
                // won't work if I used json
                // var json = JsonConvert.DeserializeObject(jsonString);
                var interimObject = JsonConvert.DeserializeObject<ExpandoObject>(jsonString);
                var jsonSerializerSettings = new JsonSerializerSettings
                {
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                };
                var myJsonOutput = JsonConvert.SerializeObject(interimObject, jsonSerializerSettings);

enter image description here

Tiny Wang
  • 10,423
  • 1
  • 11
  • 29
  • Thanks for the answer. We are trying to drop usage of Newtonsoft on the codebase, but this definitely looks way better than the current implementation that loops through the json object – Vitor Durante Nov 25 '21 at 11:35