Невозможно десериализовать Json для ввода, невозможно найти конструктор - PullRequest
1 голос
/ 11 февраля 2020

У меня возникла небольшая проблема десериализации JSON, которую я отправляю функции azure. Сначала я намереваюсь отправить массив типов зашифрованного текста с post на azure, десериализовать JSON для восстановления моих данных и затем работать с этими данными. Мой класс, как показано ниже, называется sampleClass, и у него есть атрибут ciphertext типа Ciphertext:

[DataContract]
public class sampleClass
{
    [DataMember]
    public Ciphertext ciphertext { get; set; }
    [JsonConstructor]
    public sampleClass() { }
} 

Это класс, который я пытаюсь сериализовать / десериализовать to.

Для публикации данных я использую HttpClient и отправляю их как JSON, как показано:

HttpResponseMessage response = await client.PostAsJsonAsync("api/Function1", cipher);

В моей функции azure я пытаюсь прочитать в Json как поток и десериализацию его в sampleClass [], однако это приводит к ошибке.

//Receive data from The Http PostRequest.
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

//De serialises to an object.
sampleClass[] array = JsonConvert.DeserializeObject<sampleClass[]>(requestBody);

Ошибка, которая выдается, показана ниже:

Выполнено 'Function1' (Ошибка, Id = 1be7633e-6b6a-4626-98b7-8fec98eac633) [11/02/2020 15:50:48] System.Private.CoreLib: Исключительная ситуация при выполнении функции: Function1. Newtonsoft. Json: Невозможно найти конструктор для использования для типа Microsoft.Research.SEAL.Ciphertext. Класс должен иметь конструктор по умолчанию, один конструктор с аргументами или конструктор, помеченный атрибутом JsonConstructor. Путь '[0] .ciphertext.CoeffModCount', строка 1, позиция 32.

Эта ошибка выдается при попытке десериализации моего JSON, как я могу это исправить?

1 Ответ

1 голос
/ 12 февраля 2020

У вас есть несколько проблем здесь. Давайте рассмотрим их по порядку.

Во-первых, тип Microsoft.Research.SEAL.Ciphertext не имеет ни конструктора без параметров, ни конструктора с одним параметром, как видно из справочного источника :

public class Ciphertext : NativeObject
{
    public Ciphertext(MemoryPoolHandle pool = null)
    {
        // Contents omitted
    }

    public Ciphertext(SEALContext context, MemoryPoolHandle pool = null)
    {
        // Contents omitted
    }

    // Additional constructors, methods and members omitted.

Первый параметр конструктора является необязательным, но это не означает, что он не содержит параметров, это просто означает, что компилятор предоставляет значение, когда его нет в коде. Но когда конструктор вызывается через отражение (что и делает Json. NET), все равно необходимо предоставить значение; см. Отражение - вызовите конструктор с параметрами для получения подробной информации. Отсутствие настоящего конструктора без параметров для этого типа является причиной того, что Newtonsoft. Json: невозможно найти конструктор для использования для типа Microsoft.Research.SEAL.Ciphertext. исключение.

(В комментариях было указано, что ваша проблема в том, что sampleClass не хватает конструктора по умолчанию, но этот комментарий был неправильным.)

Поскольку вы не можете изменить Ciphertext стандартным способом предоставления Ваш собственный метод построения должен использовать CustomCreationConverter<T> примерно так:

public class CiphertextConverter : CustomCreationConverter<Ciphertext>
{
    public override Ciphertext Create(Type objectType)
    {
        return new Ciphertext(); // Use the default value for the optional parameter
    }
}

И затем выполнить:

var settings = new JsonSerializerSettings
{
    Converters = { new CiphertextConverter() },
};
var array = JsonConvert.DeserializeObject<sampleClass []>(requestBody, settings);

Однако это не работает, что твоя следующая проблема. Поскольку большинство свойств *1031* publi Ciphertext доступны только для чтения, тип не может быть десериализован из них.

Сбой демонстрационной скрипки № 1 здесь .

Итак, что делать? Как оказалось, Ciphertext имеет два метода

Возможно, они позволят нам сериализовать Ciphertext в MemoryStream, а затем вставить содержимое в JSON в виде строки Base64 с помощью преобразователя, такого как:

public class CiphertextConverter : JsonConverter<Ciphertext>
{
    readonly SEALContext context;

    public CiphertextConverter(SEALContext context) => this.context = context ?? throw new ArgumentNullException(nameof(context));

    public override Ciphertext ReadJson(JsonReader reader, Type objectType, Ciphertext existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        var data = serializer.Deserialize<byte []>(reader);
        if (data == null)
            return null;
        var cipherText = new Ciphertext();
        using (var stream = new MemoryStream(data))
            cipherText.Load(context, stream);
        return cipherText;
    }

    public override void WriteJson(JsonWriter writer, Ciphertext value, JsonSerializer serializer)
    {
        using (var stream = new MemoryStream())
        {
            value.Save(stream, ComprModeType.Deflate); // TODO: test to see whether Deflate gives better size vs speed performance in practice.
            writer.WriteValue(stream.ToArray());
        }
    }
}

А затем используйте преобразователь во время сериализации и десериализации следующим образом:

var settings = new JsonSerializerSettings
{
    Converters = { new CiphertextConverter(GlobalContext.Context) },
};              
var array = JsonConvert.DeserializeObject<sampleClass []>(requestBody, settings);

Но подождите - что это за объект GlobalContext.Context? Это приводит нас к вашей третьей проблеме, а именно к тому, что вам нужны совместимые SEALContext объекты как на стороне клиента, так и на стороне сервера для передачи Ciphertext между ними через сериализацию. Теперь, глядя через демонстрационное приложение Cloud Functions , это правильное предположение, поскольку у этого приложения есть совместимые контексты как на стороне клиента, так и на стороне сервера:

Так что я собираюсь предположить, что вы тоже. Учитывая это, приведенный выше конвертер следует использовать как для сериализации, так и для десериализации.

Для целей тестирования я адаптировал метод испытания CiphertextTests.SaveLoadTest() и класс GlobalContext из https://github.com/microsoft/SEAL/tree/master/dotnet/tests для создания следующего тестового жгута:

public class TestClass
{
    [TestMethod]
    public void JsonNetSaveLoadTest()
    {
        Debug.WriteLine("Testing Json.NET");

        Func<Ciphertext, SEALContext, Ciphertext> roundtrip = (cipher, context) =>
        {
            var clientArray = new [] { new sampleClass { ciphertext = cipher } };

            var settings = new JsonSerializerSettings
            {
                Converters = { new CiphertextConverter(GlobalContext.Context) },
            };

            var requestBody = JsonConvert.SerializeObject(clientArray, settings);

            Debug.Write("   ");
            Debug.WriteLine(requestBody);
            Debug.WriteLine("   requestBody.Length={0}", requestBody.Length);

            var array = JsonConvert.DeserializeObject<sampleClass []>(requestBody, settings);

            Assert.IsTrue(array.Length == clientArray.Length);

            var reserializedJson = JsonConvert.SerializeObject(array, settings);

            Debug.Write("   ");
            Debug.WriteLine(reserializedJson);

            Assert.IsTrue(requestBody == reserializedJson);

            return array[0].ciphertext;
        };

        SaveLoadTest(roundtrip);

        Console.WriteLine("   passed.");
    }

    // Adapted from https://github.com/microsoft/SEAL/blob/master/dotnet/tests/CiphertextTests.cs#L113
    [TestMethod]
    public void DirectSaveLoadTest()
    {
        Debug.WriteLine("Testing direct save and load:");

        Func<Ciphertext, SEALContext, Ciphertext> roundtrip = (cipher, context) =>
        {
            Ciphertext loaded = new Ciphertext();

            Assert.AreEqual(0ul, loaded.Size);
            Assert.AreEqual(0ul, loaded.PolyModulusDegree);
            Assert.AreEqual(0ul, loaded.CoeffModCount);

            using (MemoryStream mem = new MemoryStream())
            {
                cipher.Save(mem);

                mem.Seek(offset: 0, loc: SeekOrigin.Begin);

                loaded.Load(context, mem);
            }
            return loaded;
        };

        SaveLoadTest(roundtrip);

        Debug.WriteLine("   passed.");
    }

    // Adapted from https://github.com/microsoft/SEAL/blob/master/dotnet/tests/CiphertextTests.cs#L113
    static void SaveLoadTest(Func<Ciphertext, SEALContext, Ciphertext> roundtrip)
    {
        SEALContext context = GlobalContext.Context;
        KeyGenerator keygen = new KeyGenerator(context);
        Encryptor encryptor = new Encryptor(context, keygen.PublicKey);
        Plaintext plain = new Plaintext("2x^3 + 4x^2 + 5x^1 + 6");
        Ciphertext cipher = new Ciphertext();

        encryptor.Encrypt(plain, cipher);

        Assert.AreEqual(2ul, cipher.Size);
        Assert.AreEqual(8192ul, cipher.PolyModulusDegree);
        Assert.AreEqual(4ul, cipher.CoeffModCount);

        var loaded = roundtrip(cipher, context);

        Assert.AreEqual(2ul, loaded.Size);
        Assert.AreEqual(8192ul, loaded.PolyModulusDegree);
        Assert.AreEqual(4ul, loaded.CoeffModCount);
        Assert.IsTrue(ValCheck.IsValidFor(loaded, context));

        ulong ulongCount = cipher.Size * cipher.PolyModulusDegree * cipher.CoeffModCount;
        for (ulong i = 0; i < ulongCount; i++)
        {
            Assert.AreEqual(cipher[i], loaded[i]);
        }
    }
}

static class GlobalContext
{
    // Copied from https://github.com/microsoft/SEAL/blob/master/dotnet/tests/GlobalContext.cs
    static GlobalContext()
    {
        EncryptionParameters encParams = new EncryptionParameters(SchemeType.BFV)
        {
            PolyModulusDegree = 8192,
            CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree: 8192)
        };
        encParams.SetPlainModulus(65537ul);
        BFVContext = new SEALContext(encParams);

        encParams = new EncryptionParameters(SchemeType.CKKS)
        {
            PolyModulusDegree = 8192,
            CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree: 8192)
        };
        CKKSContext = new SEALContext(encParams);
    }

    public static SEALContext BFVContext { get; private set; } = null;
    public static SEALContext CKKSContext { get; private set; } = null;

    public static SEALContext Context => BFVContext;
}

Рабочая демонстрационная скрипка № 2 здесь .

Примечания:

  • Пока он публикуется c, нет необходимости отмечать конструктор без параметров sampleClass с [JsonConstructor].

  • В результате тестирования строки Base64, сгенерированные для Ciphertext, выглядят довольно длинными и составляют примерно 0,5 МБ на Ciphertext. Поскольку Json. NET полностью материализует каждую строку во время синтаксического анализа, это не очень эффективно при обработке таких огромных строк. Вам придется пересмотреть свою архитектуру, если вы превысили максимальную эффективную длину строки или опыт фрагментация кучи больших объектов .

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ

Я не специалист по безопасности. Я не могу сказать вам, может ли передача сериализованного Ciphertext по проводнику утечь информацию. Также я не могу посоветовать вам, как выбрать подходящий SEALContext для вашего приложения - или даже о том, может ли наличие совместимых контекстов на стороне клиента и сервера привести к утечке информации. Этот ответ только объясняет, как сериализовать указанный c объект SEAL через Json. NET.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...