无法将 Json 反序列化为类型,无法找到构造函数

Cannot Deserialise Json to Type,Unable to find Constructor

我在反序列化发送到 azure 函数的 JSON 时遇到了一些问题。首先,我打算将 post 的密文类型数组发送到 azure,反序列化 JSON 以恢复我的数据,然后对该数据进行操作。如下所示,我的 class 称为 sampleClass,它具有 Ciphertext 类型的属性 ciphertext:

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

这是class我正在尝试Serialise/Deserialise。

到 post 我正在使用 HttpClient 的数据,我正在 post 将其作为 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);

抛出的错误如下所示:

Executed 'Function1' (Failed, Id=1be7633e-6b6a-4626-98b7-8fec98eac633) [11/02/2020 15:50:48] System.Private.CoreLib: Exception while executing function: Function1. Newtonsoft.Json: Unable to find a constructor to use for type Microsoft.Research.SEAL.Ciphertext. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path '[0].ciphertext.CoeffModCount', line 1, position 32.

当我尝试反序列化我的 JSON 时抛出此错误,我该如何解决?

你这里有几个问题。让我们按顺序排列。

首先,类型Microsoft.Research.SEAL.Ciphertext has neither a parameterless constructor nor single parameterized constructor, as can be seen from the reference source

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: Unable to find a constructor to use for type 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);

然而,这不起作用,这是你的下一个问题。由于 Ciphertext 的大多数 public 属性是只读的,因此无法从它们反序列化类型。

失败的演示 fiddle #1 here.

那么,怎么办?事实证明,Ciphertext 有两种方法

这些似乎允许我们将 Ciphertext 序列化为 MemoryStream,然后使用如下转换器将内容作为 Base64 字符串插入 JSON:

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 objects on both the client and server side to pass a Ciphertext between then via serialization. Now, looking through the Cloud Functions Demo 演示应用程序,这似乎是一个正确的假设,因为该应用程序在客户端和服务器端确实具有兼容的上下文:

所以我假设你也这样做。如果你这样做,上面的转换器应该用于序列化和反序列化。

出于测试目的,我调整了测试方法 CiphertextTests.SaveLoadTest() and the class GlobalContext from 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;
}

工作演示 fiddle #2 here.

备注:

  • 只要是public就可以了,不需要用[JsonConstructor].

    来标记sampleClass的无参构造函数
  • 根据测试,为 Ciphertext 生成的 Base64 字符串似乎很长,每个 Ciphertext 大约 0.5 MB。因为 Json.NET 在解析过程中完全具体化了每个字符串,所以它在处理如此大的字符串时并不是很有效。如果超过 maximum effective string length or experience large object heap fragmentation.

    ,您将需要重新评估您的架构

免责声明

我不是安全专家。我无法告诉您通过网络发送序列化 Ciphertext 是否会泄露信息。我也无法建议您如何为您的应用程序选择合适的 SEALContext —— 甚至在客户端和服务器端具有兼容的上下文是否会泄漏信息。此答案仅说明如何通过 Json.NET.

序列化特定的 SEAL 对象