5

Created a .Net Core 3.1 Web Application and posted request where Requested model looks like,

public class RequestPayload
    {
        public string MessageName { get; set; }

        public object Payload { get; set; }
    }

I am very new to core 3.1 and struggling to get the value of Payload property, Can anyone help me on this?

While finding the solution I also compared Newtonsoft and System.Text.Json and got Error.

Using Newtonsoft I am able to Serialize and Deserialize a model shown below,

public class RequestPayload
    {
        public string MessageName { get; set; }

        public object Payload { get; set; }

        //Problem is here -> TYPE
        public Type PayloadType { get; set; }
    }

but using System.Text.Json I am not While serializing got error "System.Text.Json.JsonException: 'A possible object cycle was detected which is not supported."

To test deserialization, somehow created JSON and tries to deserialize it using System.Text.Json but getting an error "System.Text.Json.JsonException: 'The JSON value could not be converted to System.Type. "

Used System.Text.Json.JsonSerializer, is it an issue or is there any other possibility to make this works?

5
  • Please, provide the JSON payload. Commented Jan 18, 2020 at 11:02
  • {"MessageName":"any","Payload":{"Name":"hi"}} or you are asking for serialization and desialization issue? If so ,to create that json I use newtonsoft serialization and created json , so it is proper. Commented Jan 18, 2020 at 11:44
  • So what I mean by that is to create that json I have create instance of RequestPayload class and set property whose type is TYPE then using newtonsoft serialization created json. Commented Jan 18, 2020 at 11:47
  • Why do you need to have the Type property in your class? Can you get away with not having it? Commented Jan 18, 2020 at 13:08
  • Requirements is I have one application where I will create RequestPayload and set Type property (which is not fixed this can be any class). After creating RequestPayload using rest call I will send it to second application where I only need payload type not whole instance of payload inside RequestPayload. And at second application I will use the type of payload model to parsing the contents using razor compiler. Yes there has to be different approach to achieve it. Commented Jan 18, 2020 at 14:44

1 Answer 1

15

I am very new to core 3.1 and struggling to get the value of Payload property, Can anyone help me on this?

For System.Object properties, unlike Newtonsoft.Json, System.Text.Json does not try to infer the type of the JSON payload for primitive values (such as true, 12345.67, "hello"). Similarly, for complex JSON values like objects and arrays (such as {"Name":"hi"} or [1, 2, 3]), the object property is set as a boxed JsonElement that represents the passed-in JSON. This is similar to how Newtonsoft.Json stores a JObject into the object property for complex types. See https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement?view=netcore-3.1

Like how you would with Newtonsoft.Json's JObject, you can traverse and access values within the JSON Document Object Model (DOM) using the JsonElement and call conversion APIs on it to get .NET values (such as GetProperty(String) and GetInt32()).

The following example shows how you can access the Payload values, once you have deserialized the JSON into a RequestPayload.

private static void ObjectPropertyExample()
{
    using JsonDocument doc = JsonDocument.Parse("{\"Name\":\"Darshana\"}");
    JsonElement payload = doc.RootElement.Clone();

    var requestPayload = new RequestPayload
    {
        MessageName = "message",
        Payload = payload
    };

    string json = JsonSerializer.Serialize(requestPayload);
    Console.WriteLine(json);
    // {"MessageName":"message","Payload":{"Name":"Darshana"}}

    RequestPayload roundtrip = JsonSerializer.Deserialize<RequestPayload>(json);

    JsonElement element = (JsonElement)roundtrip.Payload;
    string name = element.GetProperty("Name").GetString();
    Assert.Equal("Darshana", name);
}

While finding the solution I also compared Newtonsoft and System.Text.Json and got Error.

Even though serializing a class that contains a System.Type property is OK to do, it is not recommended, especially for web applications (there are potential issues with information disclosure though).

On the other hand, deserialization JSON into a class that contains a Type property, especially using Type.GetType(untrusted-string-input) is definitely not recommended since it introduces potential security vulnerabilities in your application.

This is why the built-in System.Text.Json intentionally does not support serializing/deserializing Type properties. The exception message you are seeing while serializing is because Type contains a cycle within its object graph and the JsonSerializer doesn't currently handle cycles. If you only care about serializing (i.e. writing) the class into JSON, you could create your own JsonConverter<Type> to add support for it (to produce the same JSON that Newtonsoft.Json would). Something like the following will work:

private class CustomJsonConverterForType : JsonConverter<Type>
{
    public override Type Read(ref Utf8JsonReader reader, Type typeToConvert,
        JsonSerializerOptions options)
    {
        // Caution: Deserialization of type instances like this 
        // is not recommended and should be avoided
        // since it can lead to potential security issues.

        // If you really want this supported (for instance if the JSON input is trusted):
        // string assemblyQualifiedName = reader.GetString();
        // return Type.GetType(assemblyQualifiedName);
        throw new NotSupportedException();
    }

    public override void Write(Utf8JsonWriter writer, Type value,
        JsonSerializerOptions options)
    {
        // Use this with caution, since you are disclosing type information.
        writer.WriteStringValue(value.AssemblyQualifiedName);
    }
}

You can then add the custom converter into the options and pass that to JsonSerializer.Serialize:

var options = new JsonSerializerOptions();
options.Converters.Add(new CustomJsonConverterForType());

Consider re-evaluating why you need the Type property on your class that is being serialized and deserialized to begin with.

See https://github.com/dotnet/corefx/issues/42712 for more information and context around why you shouldn't deserialize classes containing Type properties using Type.GetType(string).

Here is more information on how to write a custom converter: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to

An approach that can work more safely (and hence what I would recommend) is to use a type discriminator enum which contains the list of statically known types that you expect and support and explicitly create those types based on the enum values within the JsonConverter<Type>.

Here's an example of what that would look like:

// Let's assume these are the list of types we expect for the `Type` property
public class ExpectedType1 { }
public class ExpectedType2 { }
public class ExpectedType3 { }

public class CustomJsonConverterForType : JsonConverter<Type>
{
    public override Type Read(ref Utf8JsonReader reader, Type typeToConvert,
        JsonSerializerOptions options)
    {
        TypeDiscriminator typeDiscriminator = (TypeDiscriminator)reader.GetInt32();

        Type type = typeDiscriminator switch
        {
            TypeDiscriminator.ExpectedType1 => typeof(ExpectedType1),
            TypeDiscriminator.ExpectedType2 => typeof(ExpectedType2),
            TypeDiscriminator.ExpectedType3 => typeof(ExpectedType3),
            _ => throw new NotSupportedException(),
        };
        return type;
    }

    public override void Write(Utf8JsonWriter writer, Type value,
        JsonSerializerOptions options)
    {
        if (value == typeof(ExpectedType1))
        {
            writer.WriteNumberValue((int)TypeDiscriminator.ExpectedType1);
        }
        else if (value == typeof(ExpectedType2))
        {
            writer.WriteNumberValue((int)TypeDiscriminator.ExpectedType2);
        }
        else if (value == typeof(ExpectedType3))
        {
            writer.WriteNumberValue((int)TypeDiscriminator.ExpectedType3);
        }
        else
        {
            throw new NotSupportedException();
        }
    }

    // Used to map supported types to an integer and vice versa.
    private enum TypeDiscriminator
    {
        ExpectedType1 = 1,
        ExpectedType2 = 2,
        ExpectedType3 = 3,
    }
}

private static void TypeConverterExample()
{
    var requestPayload = new RequestPayload
    {
        MessageName = "message",
        Payload = "payload",
        PayloadType = typeof(ExpectedType1)
    };

    var options = new JsonSerializerOptions()
    {
        Converters = { new CustomJsonConverterForType() }
    };

    string json = JsonSerializer.Serialize(requestPayload, options);
    Console.WriteLine(json);
    // {"MessageName":"message","Payload":"payload","PayloadType":1}

    RequestPayload roundtrip = JsonSerializer.Deserialize<RequestPayload>(json, options);
    Assert.Equal(typeof(ExpectedType1), roundtrip.PayloadType);
}
Sign up to request clarification or add additional context in comments.

1 Comment

Looking for way to get property(type is object) of request body of post method. Solution you have given has serialized and deserialized in the code but at post method, model is already deserialized and I don't need property of payload model but I need payload object itself, also didn't find method to get object like ``` jsonElement.GetValue() ``` of that object.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.