Как получить значение объекта внутри объекта, используя System.Text. Json in. net core 3.1 - PullRequest
1 голос
/ 18 января 2020

Создал веб-приложение . Net Core 3.1 и отправил запрос, где запрашиваемая модель выглядит,

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

        public object Payload { get; set; }
    }

Я очень новичок в ядре 3.1 и изо всех сил пытаюсь получить значение свойства полезной нагрузки, может кто-нибудь помочь мне в этом?

При поиске решения я также сравнил Newtonsoft и System.Text. Json и получил Ошибка .

Использование Newtonsoft Я способен для сериализации и десериализации модели, показанной ниже,

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

        public object Payload { get; set; }

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

, но с использованием System.Text. Json Я не Во время сериализации получил ошибку "System.Text. Json .JsonException: 'Обнаружен возможный цикл объекта, который не поддерживается."

Чтобы проверить десериализацию , каким-то образом создал JSON и пытается десериализовать его, используя System.Text. Json, но получая ошибку "System.Text. Json .JsonException: ' Значение JSON не может быть преобразовано в System.Type. "

Используется System.Text. Json .JsonSerializer , это проблема или есть какая-либо другая возможность сделать это работает?

1 Ответ

4 голосов
/ 18 января 2020

Я очень новичок в ядре 3.1 и изо всех сил пытаюсь получить значение свойства Payload. Может ли кто-нибудь помочь мне в этом?

Для System.Object свойств, в отличие от Newtonsoft.Json, System.Text.Json не не пытается определить type полезной нагрузки JSON для примитивных значений (таких как true, 12345.67, "hello"). Аналогичным образом, для сложных JSON значений, таких как объекты и массивы (например, {"Name":"hi"} или [1, 2, 3]), свойство объекта устанавливается в рамке JsonElement, которое представляет переданный JSON. Это похоже на то, как Newtonsoft.Json сохраняет JObject в object property для сложных типов. См. https://docs.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement?view=netcore-3.1

Как и в случае с Newtonsoft. Json JObject, вы можете просматривать и получать доступ к значениям в JSON объектной модели документов (DOM), используя JsonElement и API-интерфейсы преобразования вызовов для получения значений. NET (например, GetProperty(String) и GetInt32()).

В следующем примере показано, как можно получить доступ к значениям Payload, один раз вы десериализовали JSON в 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);
}

При поиске решения я также сравнил Newtonsoft и System.Text. Json и получил ошибку.

Несмотря на то, что сериализация класса, содержащего свойство System.Type, в порядке, она не рекомендуется, особенно для веб-приложений (хотя есть потенциальные проблемы с раскрытием информации).

С другой стороны, десериализация JSON в класс, который содержит свойство Type, особенно использование Type.GetType(untrusted-string-input) - определенно не рекомендуется , так как оно представляет потенциальные уязвимости безопасности в вашем приложении. * 10 53 *

Вот почему встроенные System.Text.Json намеренно не поддерживают сериализацию / десериализацию Type свойств. Сообщение об исключении, которое вы видите при сериализации, связано с тем, что Type содержит цикл в графе объектов, а JsonSerializer в настоящее время не обрабатывает циклы. Если вам нужно только сериализовать (то есть записать) класс в JSON, вы можете создать свой собственный JsonConverter<Type>, чтобы добавить поддержку для него (для создания того же JSON, что и Newtonsoft.Json). Будет работать что-то вроде следующего:

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

Затем вы можете добавить пользовательский конвертер в опции и передать его в JsonSerializer.Serialize:

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

Рассмотреть переоценку зачем вам нужно свойство Type в вашем классе, который сериализуется и десериализуется для начала.

См. https://github.com/dotnet/corefx/issues/42712 для получения дополнительной информации и контекста, почему вы не должны ' • десериализовать классы, содержащие Type свойства, используя Type.GetType(string).

. Вот дополнительная информация о том, как написать собственный конвертер: https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to

Подход, который может работать более безопасно (и, следовательно, я бы порекомендовал ) - использовать перечисление различения типов, которое содержит список статически известных типов, которые вы ожидаете и поддерживаете, и явно создавать эти типы на основе значений перечисления в JsonConverter<Type>.

Вот пример того, как это будет выглядеть:

// 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);
}
...