6

I'm using Newtonsoft.Json to deserialize the output from my webservice to an object. It worked fine until I added a Bitmapproperty to my class (named User) to hold an avatar.

The webservice is returning that propert as a Base64 string, which is as expected. The problem is when I try to convert back the JSON from the WS to a List<User>, a JsonSerializationException is thrown in this block of code:

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

Output from exception is:

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

and looking at the inner exception:

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

It's clear that it fails to parse the Base64 string, but it's not clear why.

Any ideas/workaround?

EDIT I know I can use Convert.FromBase64String do get a byte array and load a bitmap from that. Then I'd like to update my question to ask about how can I skip or manually parse only that field. I would like to avoid, having to manually parse all the JSON. Is this even possible?

EDIT 2 I found out the root problem: JSON is not being correctly serialized in webservice (and I fail to see why). I thought that this was a somewhat different issue, but no. My webservice is simply returning a string "System.Drawing.Bitmap" instead of its base64 content. Hence the JsonSerializationException.

I have been unable to solve that issue, the only solution I've found is to turn my field into a byte [].

Community
  • 1
  • 1
Joel
  • 7,401
  • 4
  • 52
  • 58

3 Answers3

11

Read that field as string,

convert to byte array using Convert.FromBase64String and

get the image using Bitmap.FromStream(new MemoryStream(bytearray));

EDIT

You can perform image serialization/deserialization with the help of a custom converter

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

This is the 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()));
    }
}
I4V
  • 34,891
  • 6
  • 67
  • 79
  • Please refer to my edited question. Is there a way to make `JsonConvert.DeserializeObject(json)` skip a field only? This is inside a generic method, so I'd like to avoid a full manual parse... – Joel Mar 20 '13 at 11:41
  • With this code I have to manually serialize my object in the webservice, right? – Joel Mar 20 '13 at 18:17
  • @Joel no. This code deserializes base64 encoded string to Bitmap if your field/property's type is `Bitmap`. See the definition of `AClass` – I4V Mar 20 '13 at 18:18
  • see my update. Your solution is very promising, but I'm unable to test it as the root problem has changed. If I solve that problem I'll be sure to test your solution. – Joel Mar 20 '13 at 18:34
  • @Joel Just use `JsonConvert.SerializeObject(yourObject,new ImageConverter());` – I4V Mar 20 '13 at 18:37
  • Finally managed for it to work! Here's my +1. I'll signal this as the right answer. – Joel Mar 20 '13 at 19:24
7

This is my solution, I used the annotation

[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
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
Mario Buonomo
  • 167
  • 2
  • 6
0

I think that the Deserializing from Base64 to System.Drawing.Bitmap is not supported. May be you can try deserializing everything excepts the Avatar property

EDIT FOR EDITED QUESTION

Here is an interesting discussion on how to do that: JSON.Net Ignore Property during deserialization

I think the best that you can do is use Regex to remove the property from the json string:

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

and then deserialize this newJsonString without the Avatar property.

Later you can parse the original json string to get the base64 and build the 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;
}

You could improve the regular expressions or the method, but that's the basic idea.

Community
  • 1
  • 1
Agustin Meriles
  • 4,866
  • 3
  • 29
  • 44
  • "you can try deserializing everything excepts the Avatar property..." Is there a way to to this without having to manually parse all elements? – Joel Mar 20 '13 at 11:44
  • 2
    a) Base64 encoding requires 64 chars but I see only 62 in this regex `A-Za-z0-9` b) Trying to parse json with regex is not nice. c) "*I think that the Deserializing from Base64 to System.Drawing.Bitmap is not supported.*" is not correct. d) you can declare the `avatar` field as string and construct the image in c# without regex. – L.B Mar 20 '13 at 20:05