Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I create a JSON example with empty values from only an object's type?

We are doing some stuff with webhooks and events. I want to create an example of a JSON payload based on the type of the event sent to the webhook.

Lets say I have a class like this:

public class TestClass{
    public int Id {get;set;}
}

Now what I would like, is to be able to create something like

{
    "id": 0
}

just from knowing typeof(TestClass).

I do not want to create instances of my objects, because some of them are rather complex and have all sorts of parameter requirements.

I just want a JSON representation without any of the values filed in.

Any ideas?

like image 590
Jonas Olesen Avatar asked Apr 19 '26 13:04

Jonas Olesen


1 Answers

You can use Json.NET's contract resolver class to obtain a JsonContract for the type you wish to serialize. The contract defines how Json.NET serializes the type, and so can be used to generate a default representation.

A prototype implementation might looks something like the following:

public static partial class JsonExtensions
{
    readonly static IContractResolver defaultResolver = new JsonSerializer().ContractResolver;

    public static string WriteDefaultValuesForTypeToString(Type type, IContractResolver resolver = null, Formatting formatting = Formatting.None)
    { 
        using (var sw = new StringWriter())
        {
            using (var jsonWriter = new JsonTextWriter(sw) { Formatting = formatting })
            {
                WriteDefaultValuesForType(type, jsonWriter, resolver);
            }
            return sw.ToString();
        }
    }

    public static void WriteDefaultValuesForType(Type type, JsonWriter writer, IContractResolver resolver = null)
    { 
        var serializer = JsonSerializer.Create(new JsonSerializerSettings{ ContractResolver = resolver });
        WriteDefaultValuesForType(type, writer, serializer, null, 0);
    }

    static void WriteDefaultValuesForType(Type type, JsonWriter writer, JsonSerializer serializer, JsonProperty parent, int depth)
    { 
        var contract = serializer.ContractResolver.ResolveContract(type);
        var defaultValue = parent?.DefaultValue;
        if (defaultValue == null && type.IsValueType && Nullable.GetUnderlyingType(type) == null)
        {
            defaultValue = contract.DefaultCreator();
        }

        if (contract is JsonPrimitiveContract primitive)
        {
            serializer.Serialize(writer, defaultValue);
        }
        else if (contract is JsonObjectContract obj)
        {
            if (depth > 0 && defaultValue == null)
            {
                writer.WriteNull();
            }
            else
            {
                writer.WriteStartObject();

                foreach (var p in obj.Properties)
                {
                    writer.WritePropertyName(p.PropertyName);
                    WriteDefaultValuesForType(p.PropertyType, writer, serializer, p, depth++);
                }

                writer.WriteEndObject();
            }
        }
        else if (contract is JsonArrayContract array)
        {
            writer.WriteStartArray();
            writer.WriteEndArray();
        }
        else if (contract is JsonDictionaryContract dict)
        {
            if (depth > 0 && defaultValue == null)
            {
                writer.WriteNull();
            }
            else
            {
                writer.WriteStartObject();
                writer.WriteEndObject();
            }
        }
        else if (contract is Newtonsoft.Json.Serialization.JsonLinqContract linq)
        {
            /* What to do here? */
            writer.WriteNull();
        }
        else
        {
            //JsonISerializableContract, JsonDynamicContract, 
            throw new JsonSerializationException(string.Format("Unsupported contract {0}", contract));
        }
    }
}

Then, you can generate a default JSON for TestClass as follows:

var defaultJson = JsonExtensions.WriteDefaultValuesForTypeToString(typeof(TestClass), formatting : Formatting.Indented);

Notes:

  • Unusual contracts such as JsonDynamicContract or JsonISerializableContract are not implemented yet.

  • If a custom JsonConverter has been applied to the type, there is no straightforward way to generate a default serialization absent calling WriteJson() and passing in an instance of the type.

    You might be able to use FormatterServices.GetUninitializedObject(Type) to generate an empty instance of your type without calling a constructor, and then pass that into WriteJson() to generate a default serialization. No guarantee this will work though. If the converter expects members such as collections to have been initialized by properly calling the constructor, an exception will likely be thrown.

  • Similarly, there is no way to generate a default serialization for a type implementing ISerializable without an instance.

  • If you are using camel case, you can pass new CamelCasePropertyNamesContractResolver() for the resolver argument.

  • In the case of nested complex objects, WriteDefaultValuesForType() could be made recursive, however you will need to be careful to not overflow the stack in the case of recursive data models.

  • As an alternative approach, you might consider generating and using a jsonschema in your application. See e.g. Generating Schemas.

Demo fiddle here.

like image 153
dbc Avatar answered Apr 21 '26 03:04

dbc