3

This is more of a tech support question, but the newtonsoft.com website says stackoverflow is the best place to ask questions.

I have a string that was serialized with 6.0.3, and fails to deserialize with 6.0.8. It throws InvalidCastException. When I downgrade to 6.0.3, it deserializes fine; when I upgrade again to 6.0.8, the exception is repeatable.

Edit:

Rather than post the actual string, which is 10KB long and contains sensitive information, I was able to create a simple reproducible test case that demonstrates the problem.

The line that throws exception is:

this.SomeStrings = (string[])infoEnum.Current.Value;

The InvalidCastException says "Unable to cast object of type 'Newtonsoft.Json.Linq.JObject' to type 'System.String[]'"

As mentioned in the comments below, I serialized an instance of FooClass with 6.0.3, and then hard-coded the string into the asdf() method and attempt to deserialize. Deserialization succeeds on 6.0.3, and fails with InvalidCastException on 6.0.8.

Obviously, in the trivial repro case below, there is no point in doing ISerializable on FooClass, but in real life I have a need to use ISerializable in a complex data type that serializes and deserializes itself as a string array; the following is just to academically illustrate reproduction of the bug behavior.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using System.Runtime.Serialization;

namespace CBClasses
{
    [Serializable]
    public class FooClass : ISerializable
    {
        public string[] SomeStrings { get; set; }
        public FooClass() { }
        protected FooClass(SerializationInfo info, StreamingContext context)
        {
            if (info == null)
                throw new ArgumentNullException();
            SerializationInfoEnumerator infoEnum = info.GetEnumerator();
            while (infoEnum.MoveNext()) {
                SerializationEntry entry = infoEnum.Current;
                switch (entry.Name) {
                    case "SomeStrings":
                        this.SomeStrings = (string[])infoEnum.Current.Value;
                        break;
                    default:
                        throw new SerializationException("Deserialization failed on unhandled member '" + entry.Name + "'");
                }
            }
        }
        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("SomeStrings", this.SomeStrings, this.SomeStrings.GetType());
        }
    }
    public class NewtonsoftDebugTest
    {
        private static JsonSerializerSettings settings = new JsonSerializerSettings() {
            TypeNameHandling = TypeNameHandling.All,
            Formatting = Formatting.Indented,
            PreserveReferencesHandling = PreserveReferencesHandling.Objects,
            ReferenceLoopHandling = ReferenceLoopHandling.Serialize
        };
        public static void asdf()
        {
            /* FooClass foo = new FooClass() { SomeStrings = new string[0] };
             * string serializedBy603 = JsonConvert.SerializeObject(foo, settings);
             * Resulted in:
             * 
             * {
             *   "$id": "1",
             *   "$type": "CBClasses.FooClass, CBClasses",
             *   "SomeStrings": {
             *     "$type": "System.String[], mscorlib",
             *     "$values": []
             *   }
             * }
             * 
             * Now hard-coded below:
             */
            string serializedBy603 = 
                "{\n" + 
                "  \"$id\": \"1\",\n" + 
                "  \"$type\": \"CBClasses.FooClass, CBClasses\",\n" + 
                "  \"SomeStrings\": {\n" + 
                "    \"$type\": \"System.String[], mscorlib\",\n" + 
                "    \"$values\": []\n" + 
                "  }\n" + 
                "}\n";
            FooClass deserialized = (FooClass)JsonConvert.DeserializeObject(serializedBy603, settings);
            System.Diagnostics.Debugger.Break();
        }
    }
}
4
  • 5
    SO won't be able to help you without seeing either the exact string the causes the problem or a repro case that exhibits the exact same behavior. Commented Mar 1, 2015 at 1:38
  • Maybe you can tell us the type of infoEnum.Current.Value if you debug your code? Or is this the whole 10KB long string? Commented Mar 1, 2015 at 8:47
  • infoEnum.Current.Value is of type object {Newtonsoft.Json.Linq.JObject} and it has value { "$type": "System.String[], mscorlib", "$values": [] } ... Maybe the problem is actually converting String[] to string[]? ... No - I'm sure the problem is casting Newtonsoft.Json.Linq.JObject to string[], even though the value inside the JObject clearly indicates it should be a String[] Commented Mar 1, 2015 at 15:29
  • Yeah - more info in the InvalidCastException says "Unable to cast object of type 'Newtonsoft.Json.Linq.JObject' to type 'System.String[]'" Commented Mar 1, 2015 at 15:37

1 Answer 1

3

I did some investigation into this and can confirm that the problem first appeared in version 6.0.7 and is still reproducible with the latest release (9.0.1 as of this writing). The change appears to have been made as part of the commit to "Support reference preservation for ISerializable objects" from November 4, 2014. According to the source code diffs, the following code in the CreateISerializable() method of the JsonSerializerInternalReader class was changed from this:

        if (reader.TokenType == JsonToken.StartObject)
        {
            // this will read any potential type names embedded in json
            object o = CreateObject(reader, null, null, null, contract, member, null);
            serializationInfo.AddValue(memberName, o);
        }
        else
        {
            serializationInfo.AddValue(memberName, JToken.ReadFrom(reader));
        }

to this:

        serializationInfo.AddValue(memberName, JToken.ReadFrom(reader));

It seems pretty clear that the former code used to handle the embedded type metadata, whereas the replacement code does not. And, in fact, I can confirm that reverting this one section of code back to its original state fixes the problem. However, without knowing the intent of this change (maybe the metadata was supposed to be handled somewhere else in the code?), I can't recommend blindly reverting it, as that might break something else. The current serialization code still adds the type metadata as before (I get the same JSON as posted in the question), so this definitely seems to be a regression on the deserialization end. If you haven't already, you might want to report an issue on GitHub. (And yes, I do realize this question is over a year and a half old; I'm just trying to provide some closure here. ;-))

As a workaround, you can extract the string array from the SerializationEntry like this:

            this.SomeStrings = ((JObject)entry.Value)["$values"].ToObject<string[]>();
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.