Newtonsoft.Json反序列化base64图片失败

6
我在使用Newsoft.Json将从我们的Web服务中反序列化的输出转换成对象。 在我为我的类(名为User)添加一个Bitmap属性来存储头像之前,一切都工作得很好。
Web服务返回该属性作为Base64字符串,这是预期的。问题是当我尝试将WS中的JSON转换回List时,下面这段代码会抛出一个JsonSerializationException异常:
// T is IList<User>
response.Content.ReadAsStringAsync().Proceed(
    (readTask) =>
    {
        var json = ((Task<string>)readTask).Result;
        var result = JsonConvert.DeserializeObject<T>(json); //<-- it fails here

         // do stuff! 
     });

异常输出为:

Error converting value "System.Drawing.Bitmap" to type 'System.Drawing.Bitmap'. Path '[2].Avatar

并查看内部异常:

{"Could not cast or convert from System.String to System.Drawing.Bitmap."}

很明显它无法解析Base64字符串,但是原因不清楚。有什么想法/解决方法吗?
编辑:我知道我可以使用 Convert.FromBase64String 获取一个字节数组并从中加载位图。然后我想更新我的问题,询问如何跳过或手动解析仅该字段。我想避免手动解析所有JSON。这可能吗?
编辑2:我发现根本问题:JSON在Web服务中没有被正确序列化(我无法理解为什么)。我认为this是一个有点不同的问题,但不是。我的Web服务只返回一个字符串“System.Drawing.Bitmap”,而不是其base64内容。因此出现了JsonSerializationException。我一直无法解决这个问题,我找到的唯一解决方案是将我的字段转换为byte[]。
3个回答

11

将该字段作为字符串读取,

使用Convert.FromBase64String将其转换为字节数组,然后

使用Bitmap.FromStream(new MemoryStream(bytearray))获取图像。

编辑

您可以借助自定义的转换器执行图像序列化/反序列化。

public class AClass
{
    public Bitmap image;
    public int i;
}

Bitmap bmp = (Bitmap)Bitmap.FromFile(@"......");
var json = JsonConvert.SerializeObject(new AClass() { image = bmp, i = 666 }, 
                                       new ImageConverter());

var aclass = JsonConvert.DeserializeObject<AClass>(json, new ImageConverter());

这是ImageConverter

public class ImageConverter : Newtonsoft.Json.JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Bitmap);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var m = new MemoryStream(Convert.FromBase64String((string)reader.Value));
        return (Bitmap)Bitmap.FromStream(m);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        Bitmap bmp = (Bitmap)value;
        MemoryStream m = new MemoryStream();
        bmp.Save(m, System.Drawing.Imaging.ImageFormat.Jpeg);

        writer.WriteValue(Convert.ToBase64String(m.ToArray()));
    }
}

请参考我编辑后的问题。有没有办法使JsonConvert.DeserializeObject<T>(json)只跳过一个字段?这是在泛型方法中,所以我想避免完全手动解析... - Joel
我必须在Web服务中手动序列化我的对象,对吗? - Joel
@Joel 不是的。如果你的字段/属性类型是Bitmap,则此代码将将base64编码的字符串反序列化为位图。请参阅AClass的定义。 - I4V
您的解决方案非常有前途,但是由于根本问题已经改变,我无法进行测试。如果我解决了那个问题,我一定会测试您的解决方案。 - Joel
@Joel 只需使用 JsonConvert.SerializeObject(yourObject,new ImageConverter()); - I4V
终于成功了!这是我的+1。我会将其标记为正确答案。 - Joel

7
这是我的解决方案,我使用了注释。
[Serializable]
public class MyClass
{
    [JsonConverter(typeof(CustomBitmapConverter))]
    public Bitmap MyImage { get; set; }


    #region JsonConverterBitmap
    internal class CustomBitmapConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return true;
        }

        //convert from byte to bitmap (deserialize)

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            string image = (string)reader.Value;

            byte[] byteBuffer = Convert.FromBase64String(image);
            MemoryStream memoryStream = new MemoryStream(byteBuffer);
            memoryStream.Position = 0;

            return (Bitmap)Bitmap.FromStream(memoryStream);
        }

        //convert bitmap to byte (serialize)
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            Bitmap bitmap = (Bitmap)value;

            ImageConverter converter = new ImageConverter();
            writer.WriteValue((byte[])converter.ConvertTo(bitmap, typeof(byte[])));
        }

        public static System.Drawing.Imaging.ImageFormat GetImageFormat(Bitmap bitmap)
        {
            ImageFormat img = bitmap.RawFormat;

            if (img.Equals(System.Drawing.Imaging.ImageFormat.Jpeg))
                return System.Drawing.Imaging.ImageFormat.Jpeg;
            if (img.Equals(System.Drawing.Imaging.ImageFormat.Bmp))
                return System.Drawing.Imaging.ImageFormat.Bmp;
            if (img.Equals(System.Drawing.Imaging.ImageFormat.Png))
                return System.Drawing.Imaging.ImageFormat.Png;
            if (img.Equals(System.Drawing.Imaging.ImageFormat.Emf))
                return System.Drawing.Imaging.ImageFormat.Emf;
            if (img.Equals(System.Drawing.Imaging.ImageFormat.Exif))
                return System.Drawing.Imaging.ImageFormat.Exif;
            if (img.Equals(System.Drawing.Imaging.ImageFormat.Gif))
                return System.Drawing.Imaging.ImageFormat.Gif;
            if (img.Equals(System.Drawing.Imaging.ImageFormat.Icon))
                return System.Drawing.Imaging.ImageFormat.Icon;
            if (img.Equals(System.Drawing.Imaging.ImageFormat.MemoryBmp))
                return System.Drawing.Imaging.ImageFormat.MemoryBmp;
            if (img.Equals(System.Drawing.Imaging.ImageFormat.Tiff))
                return System.Drawing.Imaging.ImageFormat.Tiff;
            else
                return System.Drawing.Imaging.ImageFormat.Wmf;
        }

    }

    #endregion

0

我认为将Base64反序列化为System.Drawing.Bitmap不受支持。也许您可以尝试除Avatar属性外的所有内容进行反序列化。

编辑修改后的问题

这是一个有趣的讨论,介绍如何实现: JSON.Net Ignore Property during deserialization

我认为最好的方法是使用Regex从json字符串中删除属性:

var newJsonString = Regex.Replace(jsonString, 
                                  "(\\,)* \"Avatar\": \"[A-Za-z0-9]+\"", 
                                  String.Empty);

然后在不包含Avatar属性的情况下反序列化这个newJsonString

稍后,您可以解析原始的JSON字符串以获取base64并构建Bitmap

var avatarBase64 = Regex.Match(
                        Regex.Match(json, "(\\,)* \"Avatar\": \"[A-Za-z0-9]+\"")
                             .ToString(), 
                        "[A-Za-z0-9]+", RegexOptions.RightToLeft)
                        .ToString();

...

byte[] fromBase64 = Convert.FromBase64String(avatarBase64);
using (MemoryStream ms = new MemoryStream(fromBase64))
{
    Bitmap img = (Bitmap)Image.FromStream(ms);
    result.Avatar = img;
}

你可以改进正则表达式或方法,但这是基本思路。


你可以尝试对除 Avatar 属性以外的所有内容进行反序列化,而无需手动解析所有元素。 - Joel
2
a) Base64编码需要64个字符,但我在这个正则表达式A-Za-z0-9中只看到了62个。b) 试图使用正则表达式解析JSON不好。c) "我认为从Base64反序列化到System.Drawing.Bitmap不受支持。"是不正确的。d) 您可以将avatar字段声明为字符串,并在C#中构造图像而无需使用正则表达式。 - L.B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接