将值从字符串转换为流时出错

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了将值从字符串转换为流时出错相关的知识,希望对你有一定的参考价值。

我想序列化一个自定义对象:

public class MyCustomObject
{
    public string Name { get; set; }
    public DateTime Date { get; set; }
    public List<HttpPostedFileBase> Files { get; set; }

    public MyCustomObject()
    {
        Files = new List<HttpPostedFileBase>();
    }
}

在json。为此,我使用自定义转换器:

public class HttpPostedFileConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var stream = (Stream)value;
        using (var sr = new BinaryReader(stream))
        {
            var buffer = sr.ReadBytes((int)stream.Length);
            writer.WriteValue(Convert.ToBase64String(buffer));
        }
    }

我使用JsonSerializerSettings序列化到json.net知道哪个类型的工具(对于HttpPostedFileBase)。

        var settings = new JsonSerializerSettings();
        settings.Converters.Add(new HttpPostedFileConverter());
        settings.TypeNameHandling = TypeNameHandling.Objects;

该对象已正确序列化,但序列化时出现此错误:

JsonSerializationException Error converting value "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAIBAQEBAQIBAQECAgICAgQDAgICAgUEBAMEBgUGBgYFBgYGBwkIBgcJBwYGCAsICQoKCgoKBggLDAsKDA

这是我序列化对象的值:

{
  "$type": "ConsoleApplication1.MyCustomObject, ConsoleApplication1",
  "Name": "Test2",
  "Date": "2016-11-03T12:35:14.6020154+01:00",
  "Files": [
    {
      "$type": "System.Web.HttpPostedFileWrapper, System.Web",
      "ContentLength": 1024,
      "FileName": "Pannigale.jpg",
      "ContentType": "image/jpg",
      "InputStream": "/9j/4AAQ...KKAP//Z"
    }
  ]
}

反序列化有什么问题?

编辑我测试了一个类来测试...现在它的工作原理:

public class TestHttpFile : HttpPostedFileBase
{
    string fullFileName = @"C:PicturesSBK-1299-Panigale-S_2015_Studio_R_B01_1920x1080.mediagallery_output_image_[1920x1080].jpg";
    public override int ContentLength
    {
        get
        {
            return 1024;
        }
    }

    public override string FileName
    {
        get
        {
            return "Pannigale.jpg";
        }
    }

    public override string ContentType
    {
        get
        {
            return "image/jpg";
        }
    }

    public override Stream InputStream
    {
        get
        {
            return File.OpenRead(fullFileName);
        }
    }
}

在序列化中,我注意到了这种差异:

 "$type": "ConsoleApplication1.TestHttpFile, ConsoleApplication1",

代替

 "$type": "System.Web.HttpPostedFileWrapper, System.Web",

但最后我不想创建一个包装器或其他......我不明白为什么它适用于这种类型而不是HttpPostedFileWrapper

答案

为什么反序列化失败

简而言之:您尝试反序列化的类型,即HttpPostedFileWrapper及其潜在的HttpPostedFile,不是可公开构建的。

大概你的HttpPostedFileConverter实际上是一个流转换器,并且工作原理如下:

public class StreamConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(Stream).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var bytes = serializer.Deserialize<byte[]>(reader);
        return new MemoryStream(bytes);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var stream = (Stream)value;
        var bytes = stream.ReadAllBytesAndReposition();
        serializer.Serialize(writer, bytes);
    }
}

public static class StreamExtensions
{
    public static byte[] ReadAllBytesAndReposition(this Stream stream)
    {
        const int bufferSize = 4096;
        using (var ms = new MemoryStream())
        {
            byte[] buffer = new byte[bufferSize];
            int count;
            var position = stream.CanSeek ? stream.Position : (long?)null;
            while ((count = stream.Read(buffer, 0, buffer.Length)) != 0)
                ms.Write(buffer, 0, count);
            if (position != null)
            {
                // Restore position
                stream.Position = position.Value;
            }
            return ms.ToArray();
        }
    }
}

在这种情况下,我可以为你的MyCustomObject类生成类似于你的JSON,假设我还在序列化器设置中设置了TypeNameHandling = TypeNameHandling.Auto。但是,反序列化将失败,因为具体类型System.Web.HttpPostedFileWrapper只有一个构造函数,它需要一个HttpPostedFile。来自reference source

public class HttpPostedFileWrapper : HttpPostedFileBase {

    private HttpPostedFile _file;

    public HttpPostedFileWrapper(HttpPostedFile httpPostedFile) {
        if (httpPostedFile == null) {
            throw new ArgumentNullException("httpPostedFile");
        }
        _file = httpPostedFile;
    }

由于构造函数是公共的,Json.NET将调用它,但由于JSON中没有名为httpPostedFile的属性,它将为该值传递null,从而导致抛出ArgumentNullException

这将我们带到下一个问题:HttpPostedFile没有公共构造函数。相反,要直接创建一个,您必须通过反射调用几个不同的Microsoft内部方法,如How to instantiate a HttpPostedFile中所示。因此,即使你为custom JsonConverter创建了HttpPostedFileWrapper,在内部构造必要的HttpPostedFile也是非常棘手的。 Json.NET肯定不能自动完成。

那么,你有什么选择?

选项1:创建自己的HttpPostedFilesBase子类型

您可以创建自己的HttpPostedFilesBase子类型,可以成功序列化和反序列化,然后在序列化期间使用适当的HttpPostedFilesBaseJsonConverter的所有实例映射到此类型。以下类型MemoryHttpPostedFile执行此操作:

public sealed class MemoryHttpPostedFile : HttpPostedFileBase
{
    readonly string contentType;
    readonly string fileName;
    readonly MemoryStream inputStream;

    public MemoryHttpPostedFile(string contentType, string fileName, [JsonConverter(typeof(StreamConverter))] MemoryStream inputStream)
    {
        if (inputStream == null)
            throw new ArgumentNullException("inputStream");
        this.contentType = contentType;
        this.fileName = fileName;
        this.inputStream = inputStream;
    }

    public override int ContentLength { get { return (int)inputStream.Length; } }

    public override string ContentType { get { return contentType; } }

    public override string FileName { get { return fileName; } }

    [JsonConverter(typeof(StreamConverter))]
    public override Stream InputStream { get { return inputStream; } }

    //TODO: implement SaveAs()
    public override void SaveAs(string filename)
    {
        // Implement based on HttpPostedFile.SaveAs()
        // https://referencesource.microsoft.com/#System.Web/HttpPostedFile.cs,678e7f8bc95c149f
        throw new NotImplementedException();
    }
}

public class HttpPostedFileBaseConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(HttpPostedFileBase).IsAssignableFrom(objectType)
            && !typeof(MemoryHttpPostedFile).IsAssignableFrom(objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var postedFile = (HttpPostedFileBase)value;

        // Save position
        var wrapper = new MemoryHttpPostedFile(postedFile.ContentType, postedFile.FileName, new MemoryStream(postedFile.InputStream.ReadAllBytesAndReposition()));

        serializer.Serialize(writer, wrapper);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var wrapper = serializer.Deserialize<MemoryHttpPostedFile>(reader);
        return wrapper;
    }
}

由于HttpPostedFilesBase的所有子类型都映射到MemoryHttpPostedFile,因此不再需要设置TypeNameHandling.Auto。注意我在本答案前面使用的是StreamConverterStreamExtensions

将转换器添加到如下设置:

var settings = new JsonSerializerSettings
{
    Converters = new JsonConverter[] { new HttpPostedFileBaseConverter() },
    Formatting = Formatting.Indented,
};

选项2:实际上序列化和反序列化HttpPostedFileWrapper

要做到这一点,我们需要使用this answerHow to instantiate a HttpPostedFile相当精细和“hacky”的代码到paracycle,如下所示:

public class HttpPostedFileBaseConverter : JsonConverter
{
    class HttpPostedFileSurrogate
    {
        public string ContentType { get; set; }

        public string FileName { get; set; }

        public byte[] InputStream { get; set; }
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(HttpPostedFileBase).IsAssignableFrom(objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var wrapper = (HttpPostedFileWrapper)value;

        // Save position
        var surrogate = new HttpPostedFileSurrogate
        {
            ContentType = wrapper.ContentType,
            FileName = wrapper.FileName,
            InputStream = wrapper.InputStream.ReadAllBytesAndReposition(),
        };

        serializer.Serialize(writer, surrogate);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var surrogate = serializer.Deserialize<HttpPostedFileSurrogate>(reader);
      

以上是关于将值从字符串转换为流时出错的主要内容,如果未能解决你的问题,请参考以下文章

如何将值从一个片段的回收器视图项传递到另一个片段

如何将值从片段/活动传递到 xml?

在字典中,将值从字符串转换为整数

如何将值从其他活动传递给 viewpager 片段?

将值从字符转换为数字或从数字转换为字符

如何将值从一个活动中的片段传递到另一个活动? [复制]