Search code examples
c#json.netsystem.text.json

System.Text.Json: How do I serialize an object to be a nested subobject with a key?


I have an object of the following class that I want to serialize:

[Serializable]
public class BuildInfo
{
    public DateTime BuildDate { get; set; }

    public string BuildVersion { get; set; }
}

I wrote the following method to serialize any objects:

...
public static string JsonSerialize<TValue>(TValue value)
{
    var options = new JsonSerializerOptions
    {

        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        WriteIndented = true
    };

    return JsonSerializer.Serialize(value, options);
}
...

I got the following output (example):

{
    "buildDate": "2021-04-22T17:29:59.1611109+03:00",
    "buildVersion": "1.0.1"
}

I want to get output like this:

{
    "buildInfo": {
        "buildDate": "2021-04-22T17:29:59.1611109+03:00",
        "buildVersion": "1.0.1"
    }
}

How can I do it?


Solution

  • Taking all the answers and comments into account, I got the following code:

    public static class JsonUtilities
    {
        public static string JsonSerializeTopAsNested<TValue>(TValue? value, JsonSerializerOptions? options = null)
        {
            return JsonSerializer.Serialize(ToSerializableObject(value, options), options);
        }
    
        public static async Task<string> JsonSerializeTopAsNestedAsync<TValue>(TValue? value,
            JsonSerializerOptions? options = null, CancellationToken cancellationToken = default)
        {
            await using var stream = new MemoryStream();
            await JsonSerializer.SerializeAsync(stream, ToSerializableObject(value, options), options,
                cancellationToken);
    
            return Encoding.UTF8.GetString(stream.ToArray());
        }
    
        public static TValue? JsonDeserializeTopAsNested<TValue>(string json, JsonSerializerOptions? options = null)
        {
            var serializableObject = JsonSerializer.Deserialize<Dictionary<string, TValue?>>(json, options);
    
            return FromSerializableObject(serializableObject, options);
        }
    
        public static async Task<TValue?> JsonDeserializeTopAsNestedAsync<TValue>(string json,
            JsonSerializerOptions? options = null, CancellationToken cancellationToken = default)
        {
            await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
            var serializableObject = await JsonSerializer.DeserializeAsync<Dictionary<string, TValue?>>(stream, options,
                cancellationToken);
    
            return FromSerializableObject(serializableObject, options);
        }
    
        private static Dictionary<string, TValue?> ToSerializableObject<TValue>(TValue? propertyValue,
            JsonSerializerOptions? options)
        {
            var propertyName = GetPropertyName<TValue>(options);
    
            return propertyValue == null && options is {IgnoreNullValues: true}
                ? new Dictionary<string, TValue?>()
                : new Dictionary<string, TValue?> {[propertyName] = propertyValue};
        }
    
        private static TValue? FromSerializableObject<TValue>(IReadOnlyDictionary<string, TValue?>? serializableObject,
            JsonSerializerOptions? options)
        {
            var propertyName = GetPropertyName<TValue>(options);
    
            return serializableObject != null && serializableObject.ContainsKey(propertyName)
                ? serializableObject[propertyName]
                : default;
        }
    
        private static string GetPropertyName<TValue>(JsonSerializerOptions? options)
        {
            var propertyName = typeof(TValue).Name;
            return options?.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName;
        }
    }
    

    I would appreciate additional comments and bugs found.