Search Unity

JSON .NET for Unity

Discussion in 'Assets and Asset Store' started by Dustin-Horne, Sep 13, 2013.

  1. ForceMagic

    ForceMagic

    Joined:
    Feb 27, 2015
    Posts:
    38
    Hello, I just updated to the latest version of JSON.Net. We had some issues with the older version 1.5.0 when serializing Unity object with Unity 5.3.4f1. However, even after the update we still have those issues such as :

    [Data] JsonConvert.SerializeObject failed on DataItem: 0, ex: Newtonsoft.Json.JsonSerializationException: Error getting value from 'rigidbody' on 'UnityEngine.GameObject'. ---> System.NotSupportedException: rigidbody property has been deprecated

    [Data] JsonConvert.SerializeObject failed on DataItem: 0, ex: Newtonsoft.Json.JsonSerializationException: Error getting value from 'linear' on 'UnityEngine.Color'. ---> System.StackOverflowException

    Have you encounter those issue or are you aware of them?

    I can send you the full call stack if you wish, we could communicate by email if it is easier for you.

    Thanks in advance.
     
  2. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    You cannot directly serialize GameObject or Monobehaviors. They have some legacy properties on them such as "rigidbody". The problem is that Unity overrides the equality check for null (for one), and for two they have deprecated those properties. So, in Unity 4.x, Unity only pretends they are null... the serializer uses reflection so it knows they aren't null and tries to serialize them anyway, in which case Unity throws an exception because it hasn't been added.

    In Unity 5.x, they have deprecated these fields but left them on the object and they throw an exception when you try to access them. Unfortunately, they throw an exception when the serializer tries to access them because it still sees them as members when it reflects them.

    There are a couple of ways around that currently. Firstly, you can create a "Proxy" class that mirrors the members you want to serialize and you can copy the members to that and serialize it instead. Secondly you could create your own JsonConverter that skips those properties.

    My first priority was to get version 2.0 out the door with an upgraded profile. I will have a 2.0.1 update coming very soon that has the mapping for AppleTV so you don't have to manually map it on 5.3.5 and up. Once that's out the door (in the next day or two) I'm going to start looking into whether it will be feasible to build additional support for GameObject and MonoBehavior classes in as custom converters or contract resolvers.

    By the way, the second issue you're going to run into is that you won't be able to directly deserialize those classes either because there's no way to add an existing object to the scene (you have to Instantiate). You could instantiate and then use PopulateObject but you'd have to be careful because you don't want to break the references to the transform hierarchy.
     
  3. ForceMagic

    ForceMagic

    Joined:
    Feb 27, 2015
    Posts:
    38
    Awesome, thanks for the quick reply, that's what I thought, but since I saw in your code that you were managing some of the Unity type to detect Circular references, I was not sure if it was meant to be handled by JSON.Net for Unity.

    Regards
     
  4. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Yeah, that was meant to handle auto-properties like those in Vector3 for example that return another Vector3 (normalized), same with Matrix4x4, etc.

    You can take a look at the source code in the included zip file. In the /Newtonsoft.Json/Converters folder are several different converters that handle things in different ways. The VectorConverter and Matrix4x4Converter are good examples of Unity specific converters.

    If you'd like some help tackling the issue or writing a custom converter just shoot me an email. The contact info is on the last page of the included documentation.

    I've also attached the documentation to this post in case anyone is interested to see some of the capabilities of 2.0.
     

    Attached Files:

  5. tswalk

    tswalk

    Joined:
    Jul 27, 2013
    Posts:
    1,109
    Actually I'm running 5.3.2p4; however, I have no issues with updating that if that is the cause... :)
     
  6. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    I'll test it in 5.3.2p4. I built and published (and tested) against UWP with 5.3.0 so that shouldn't be an issue unless there's a bug in 5.3.2. I also tested in 5.3.5...
     
  7. imtrobin

    imtrobin

    Joined:
    Nov 30, 2009
    Posts:
    1,548
    Hi can u update the v2 for those who purchased from your website
     
  8. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Yes, I'll have V2 available on my website tomorrow.
     
  9. tswalk

    tswalk

    Joined:
    Jul 27, 2013
    Posts:
    1,109
    Hi Dustin, just a short update.. I upgraded to version 5.3.5p5 and still had the same issue.

    to just rule out any SDK potential issues, I reformatted and re-installed Windows 10 and Visual Studio 2015 Update 2 >.<

    while I'm not having any troubles with Universal 10 Apps, and JSON.NET works great with my IL2CPP builds (both iOS and Universal 10)... I just can not get it to build for .NET :( Same errors regarding the mscorlib thing with SerializationBinder.
     
  10. mr_zog

    mr_zog

    Joined:
    Jan 21, 2014
    Posts:
    165
    Not sure if it helps, but I use Unity 5.3.3p2 for Win 8.1 (WSA) and Unity 5.3.4f1 for Win 10 (UWP) builds and it worked for us. Haven't tried 5.3.5.
     
    Dustin-Horne likes this.
  11. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Hmm there is something odd with a reference somewhere. I wonder if one of your custom serialization binders is referencing something that's not available in CoreCLR.
     
  12. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Also as a quick update, we've discovered a bug that affects some AOT platforms, possibly all in some cases. There were a couple of generic interface implementations that AOT didn't like. They work fine with il2cpp but not with the mono Backend.

    This has been fixed and I'll be submitting an update today but if anyone is running into errors please let me know.
     
  13. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    @tswalk - I'm taking a closer look at the repro you sent me to see if I can reproduce / narrow down the issue. I think it's something in one of the custom binders that is referencing something that's probably not available in UWP.
     
    tswalk likes this.
  14. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    --UPDATE--
    I've just submitted version 2.0.1 to the asset store and it's pending review. I've expanded Unity 4.x support now back to 4.6 instead of just 4.7.2. I also added Apple TV (tvOS) mapping and some significant bug fixes for consoles (and possibly other AOT platforms) when using the Mono .NET Backend. This version was published with Unity versions:
    4.6.0, 5.0.0, 5.1.0, 5.2.0, 5.3.0 and 5.3.5 to catch the full range of platform mappings. I also changed the versioning semantics of the DLL to match the official repository so you can reference the official JSON .NET in a class library project that's shared on a web server or application. As long as it's referencing 8.x there should no longer be a versioning error when it loads with my port since the product guids and now the versioning match 1:1.

    If you upgrade from one Unity version to another you should pull from the asset store again to make sure you have the most up to date platforms mapped.

    Here is the changelog:
    === Version 2.0.1 ===
    ** Expanded Legacy support to Unity 4.6 and above (previously was 4.7.2 and above)
    ** Added tvOS Platform Mapping
    ** Changed DLL Versions to 8.0.0.0 to match versioning semantics of official JSON .NET Release
    ** Fixed a bug where runtime errors were thrown on consoles due to generic IEquatable(Of T) usage.
    ** Changed Assembly loading to improve loading of classes from external assemblies

    @Dantus - Just tagging you in case you stopped following the thread... I know you were interested in the progress in updating the asset to Json .NET 7.x which we skipped and went right to 8.x.
     
  15. tswalk

    tswalk

    Joined:
    Jul 27, 2013
    Posts:
    1,109
    Thanks Dustin, please just email me or ping here if there's anything I can do on my end to help...
     
  16. robertwahler

    robertwahler

    Joined:
    Mar 6, 2013
    Posts:
    43
    I'd like to remove the AssemblyName when using TypeNameHandler.Objects similar to this StackOverflow question.

    http://stackoverflow.com/questions/...m-the-type-name-while-serializing-and-deseria

    My problem is the JSON.NET for Unity seems to be ignoring the "Binder" setting when I create my own JsonSerializerSettings class. Basically, I'd like to not have an AssemblyName and I'll just iterate over loaded assemblies to find my types if needed.

    Any ideas? Thanks!
     
  17. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    I'm travelling at the moment and about 5 hours or so from being home but I'll have a look. Are you using the new 2.0 version?
     
  18. robertwahler

    robertwahler

    Joined:
    Mar 6, 2013
    Posts:
    43
    Yes. I'm using 2.0. Thanks for looking into this.
     
  19. robertwahler

    robertwahler

    Joined:
    Mar 6, 2013
    Posts:
    43
    I put up a simple unit test case to show what I'm trying to do.

    https://gist.github.com/robertwahler/b3edba62493672c058955bbce07d5790

    It won't compile under Unity, it looks like the useful bits of the DefaultSerializationBinder are stripped out of the DLL via compiler defines.
     
  20. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Oh, I see what's going on. You're trying to override the BindToName method. The problem is, BindToName wasn't added to the .NET Framework until .NET 4. Unity uses a version of Mono that is based on .NET 3.5 so it doesn't have BindToName.

    The reason it's directive'd out, if you look the define is:
    #if !(NET35 || NET20)

    Now, on the windows platforms (Windows 8.1 Universal and UWP), they actually use .NET 4.5, so the code is still in there. If you wanted to use the functionality you could on, say a UWP build, but you'd have to use similar directives to mark out that code in your own SerializationBinder directive and it would behave differently in the editor and all other platforms than it would in UWP or 8.1 Universal.

    Now as far as the binder being ignored, I have to look at that to see why it would still be using your serialization binder with your BindToType implementation.
     
  21. robertwahler

    robertwahler

    Joined:
    Mar 6, 2013
    Posts:
    43
    Thanks for looking into this. The entire Binder is ignored in Unity. The implementation I posted on Github doesn't work. I just would like it to work. I really think being able to control the type manually at deserialization is important, especially if you are sending JSON over the wire.

    Do you have an idea how much effort would be involved in creating a special binder class just for Unity? Alternatively, how about removing the compiler defines that prevent BindToType from being used?

    Maybe someone else has an alternate solution?
     
  22. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    What platform are you building for?
     
  23. robertwahler

    robertwahler

    Joined:
    Mar 6, 2013
    Posts:
    43
    We build for the common Steam platforms Mac/Win/Linux as well as iOS, tvOS and Android.
     
  24. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    This is where I'm a little confused... and I'm setting up a project right now to run a test. BindToType is not taken out by any defines.. not anywhere. In DefaultSerializationBinder it looks like this:

    Code (csharp):
    1.  
    2.         /// <summary>
    3.         /// When overridden in a derived class, controls the binding of a serialized object to a type.
    4.         /// </summary>
    5.         /// <param name="assemblyName">Specifies the <see cref="T:System.Reflection.Assembly"/> name of the serialized object.</param>
    6.         /// <param name="typeName">Specifies the <see cref="T:System.Type"/> name of the serialized object.</param>
    7.         /// <returns>
    8.         /// The type of the object the formatter creates a new instance of.
    9.         /// </returns>
    10.         public override Type BindToType(string assemblyName, string typeName)
    11.         {
    12.             return _typeCache.Get(new TypeNameKey(assemblyName, typeName));
    13.         }
    14.  
    15. #if !(NET35 || NET20)
    16.         /// <summary>
    17.         /// When overridden in a derived class, controls the binding of a serialized object to a type.
    18.         /// </summary>
    19.         /// <param name="serializedType">The type of the object the formatter creates a new instance of.</param>
    20.         /// <param name="assemblyName">Specifies the <see cref="T:System.Reflection.Assembly"/> name of the serialized object. </param>
    21.         /// <param name="typeName">Specifies the <see cref="T:System.Type"/> name of the serialized object. </param>
    22.         public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
    23.         {
    24. #if (DOTNET || PORTABLE)
    25.             assemblyName = serializedType.GetTypeInfo().Assembly.FullName;
    26.             typeName = serializedType.FullName;
    27. #else
    28.             assemblyName = serializedType.Assembly.FullName;
    29.             typeName = serializedType.FullName;
    30. #endif
    31.         }
    32. #endif
    33.  
    And it's actually used in ResolveTypeName of JsonSerializerInternalReader as so:

    Code (csharp):
    1.  
    2. specifiedType = Serializer._binder.BindToType(assemblyName, typeName);
    3.  
    That code executes if TypeNameHandling is anything other than "None".

    Now before that it resolves the assemblyname and typename by looking for the delimiter. If the delimter doesn't exist in the string then it sets the whole type to the typename, otherwise it splits it into assemblyname and typename.

    Code (csharp):
    1.  
    2.             int? assemblyDelimiterIndex = GetAssemblyDelimiterIndex(fullyQualifiedTypeName);
    3.  
    4.             if (assemblyDelimiterIndex != null)
    5.             {
    6.                 typeName = fullyQualifiedTypeName.Substring(0, assemblyDelimiterIndex.GetValueOrDefault()).Trim();
    7.                 assemblyName = fullyQualifiedTypeName.Substring(assemblyDelimiterIndex.GetValueOrDefault() + 1, fullyQualifiedTypeName.Length - assemblyDelimiterIndex.GetValueOrDefault() - 1).Trim();
    8.             }
    9.             else
    10.             {
    11.                 typeName = fullyQualifiedTypeName;
    12.                 assemblyName = null;
    13.             }
    14.  
    So that logic is already handled by Json .Net. I just need to step through the code and see why it wouldn't be hitting your BindToType method.
     
  25. robertwahler

    robertwahler

    Joined:
    Mar 6, 2013
    Posts:
    43
    Is Newtonsoft.Json.Utilities.ReflectionUtils.GetTypeName() used in stock deserialization? If so, line 161 of ReflectionUtils from version 2.0 has the entire binder defined out for NET30 and NET35, it uses the AssemblyQualifiedName regardless.

    I've fixed my gist. It should be enough to test this functionality. The gist unit test fails because the assembly name is not removed as it should be if the binder was used.

    https://gist.github.com/robertwahler/b3edba62493672c058955bbce07d5790
     
  26. robertwahler

    robertwahler

    Joined:
    Mar 6, 2013
    Posts:
    43
    You are right. I was trying to override a non-virtual method.

    I've fixed the gist by removing the override keyword. The BindToName method does exist for .NET 3.5 in abstract form in the base class SerializationBinder so I'm not sure why we can't use the binder as long as we provide a concrete implementation for BindToName in NET35.
     
  27. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Yes, but that's using BindToName which isn't supported until .NET 4 which Unity does not use (except in Windows Apps / UWP which is 8.1 and 10 Universal respectively).

    Otherwise it uses type AssemblyQualifiedName of the type. That method is also only used during Serialization, not during Deserialization. On your JsonSerializerSettings you need to change TypeNameAssemblyFormat to FormatterAssemblyStyle.Simple in which case the assembly name won't be included when it's serialized. That's why you're ending up with your assembly name in your Json. You can't override BindToName because it's a .NET 4.0 feature and it won't be used in anything except Windows Store / Windows Universal applications. You should in fact receive an error (or warning can't recall which) that says "No suitable method to override" when you're trying to override BindToName.

    In your example, initialize your Serializer like this:

    Code (csharp):
    1.  
    2. private JsonSerializerSettings serializerSettings = new JsonSerializerSettings() {
    3.     // http://www.newtonsoft.com/json/help/html/SerializationSettings.htm
    4.     // write $type for objects but not collections
    5.     TypeNameHandling = TypeNameHandling.Objects,
    6.     TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple, //<-- Added This Line
    7.     Binder = new TypeNameHandler(),
    8. };
    9.  
    Get rid of your BindToName override and just use your BindToType.
     
  28. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Theoretically I could make this work by changing the implementation to use an interface as well which includes BindToName when not building against .NET 4.0... but that would be a nightmare because it would be a breaking change. Anyone who wanted to use it would have to override for .NET 4+, or just implement for 3.5... it's not a good solution.

    Edit: Alternately I guess I could implement it as an extension method in .NET 3.5, but then it would not be overridable. So I would risk breaking other valid implementations by changing how this works at the core.
     
  29. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Look... here is what GetTypeName does during serialization:

    Code (csharp):
    1.  
    2.            switch (assemblyFormat)
    3.             {
    4.                 case FormatterAssemblyStyle.Simple:
    5.                     return RemoveAssemblyDetails(fullyQualifiedTypeName);
    6.                 case FormatterAssemblyStyle.Full:
    7.                     return fullyQualifiedTypeName;
    8.                 default:
    9.                     throw new ArgumentOutOfRangeException();
    10.             }
    11.  
    If you're setting it to Simple, there's no reason to need the BindToName as that is only used during Serialization... Because that block of code which is lines 176 through 184 in ReflectionUtils, actually calls RemoveAssemblyDetails which will strip it down to just the type name which is what you're attempting to do in BindToName anyway.
     
  30. robertwahler

    robertwahler

    Joined:
    Mar 6, 2013
    Posts:
    43
    Maybe I'm doing something wrong? I think Simple is the default but I changed my gist unit test to include Simple explicitly and it still fails with the assembly name included in the JSON. I think RemoveAssemblyDetails() doesn't consider the assembly name a detail.

    Here is the updated gist with Simple defined:
    https://gist.github.com/robertwahler/b3edba62493672c058955bbce07d5790

    I can live without BindToName even though it is defined in abstract for NET35 (I don't see why it can't be used). I can use a Regex hack to remove the AssemblyName. I can't live without type name control at deserialization and allowing the Binder to be consulted in NET35 seems like it should work.

    I can imagine backward compatibility as long as there are checks to see if the binder exists and defines all the appropriate methods.
     
  31. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Oh I see. Yeah, when Simple is used it still includes the basic assembly name but not all of the other assembly details. So, the problem is really that BindToName is not available. I'm not sure where you are seeing it as abstract... it's "virtual" in SerializationBinder but is not available in .NET 3.5.
    https://msdn.microsoft.com/en-us/li...serializationbinder.bindtoname(v=vs.110).aspx

    I could potentially add support for this functionality but there are some issues with it as follows:

    1. Adding it as a standard method without the override would fail in Windows Store applications. I'm also prepared for when Unity upgrades the .NET Profile as in this brand new port I did not eliminate .NET 4 supported code but used defines to make sure it isn't in the final assemblies.
    2. Adding it to an interface and using an interface would essentially do the same as #1 above...
    3. I could add it as separate standalone functionality but it would be a considerable effort as I would need to add a whole new setting for serialization settings and I would need to check it all the way down the line as it potentially conflicts with other settings. I can't just added to AssemblyFormatterStyle because it's a built in enumeration type. I can't just simply add a new setting because if someone changed that setting without knowing what it does and didn't create their own SerializationBinder implementation it would cause runtime exceptions and result in more support requests for me.
    4. The above 3 would also be a considerable divergence from the official Json .Net release and I'm striving as much as possible to stay with the standard well known API. There are a couple of edge cases, such as the built in default converters to allow serialization and deserialization of Vectors, etc.. but they aren't an API change.
    I think the best approach for me here is to sadly say that your particular scenario isn't currently supported because of Unity's old runtime version. There are a couple of workarounds you could use:

    1. Create a custom JsonConverter for your type(s) that you're interested in and resolve and write the $type property yourself.
    2. Override the BindToType in your binder as you've done and ignore the assemblyName value. But the assembly name will still be present in your json.
     
  32. tswalk

    tswalk

    Joined:
    Jul 27, 2013
    Posts:
    1,109
  33. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    it's available, and I may have found the source of your issue as well. I haven't tested yet but you may need a slight change to your custom binder. I'm not sure of this will fix your build issues but see here:
    https://social.msdn.microsoft.com/F...lease-built-cvb-uwp?forum=Win10SDKToolsIssues
     
  34. tswalk

    tswalk

    Joined:
    Jul 27, 2013
    Posts:
    1,109
    I noticed that post and tried to add the decoration on the override... it didn't work, Unity would still throw the 'missing from mscorlib' error thing.
     
  35. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Ok, I figured out what the problem is and I can reliably reproduce it but I don't know if you're going to like it. :) The problem is the reference rewriter in Unity. If you create a custom SerializationBinder in your Unity project, it throws the error when it tries to compile. The only way you're going to get around that is by essentially doing the same thing I did with my asset. You're going to need to create a solution in visual studio and you're going to need to create a couple of projects... but both projects should share the same files.

    One project needs to be a Portable Class Library project targetting .NET 4.5. I can't remember which PCL version it has to target off the top of my head, but you can look in the source code of Json .Net that I included in the release and look at the Windows project.

    The other project will be a standard class library targetting .NET 3.5. Both projects will include your serialization binders and you'll have to build both DLLs. Then put them in your project... Map one DLL to Windows 10 Universal with .NET Backend and map everything else (including UWP with IL2CPP Backend) to the other DLL.

    That's the only way you'll get this to work because Unity is puking when it's trying to rewrite the references. You should also submit that as a bug to Unity because it should build.
     
    tswalk likes this.
  36. tswalk

    tswalk

    Joined:
    Jul 27, 2013
    Posts:
    1,109
    oi! ok, thanks yet again Dustin for the awesome support m8.

    I submitted a bug on it all here: (Case 809014)

    I actually have not made a PCL yet, but why not now eh? :)

    just to get the order straight in my mind, the PCL version will be the one I need to map for use with Universal 10 (UWP with .NET scripting backend), and the standard class library basically for everything else (IL2CPP builds).. yes?
     
  37. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Exactly. It was getting late last night so I had to call it a night but I'll setup vs solution for you with the two projects you're after. It's a bit of a pain because you have to edit the pcl csproj file manually (just once) to set the base class library version / variant number.
     
  38. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    @tswalk - I just sent you an email so let me know if you don't get it. I created a VS2015 solution for you with the PCL configuration and the standalone project. I also included my updated 2.0.1 update because assembly versioning changed to be consistent so I wanted to make sure I referenced the correct one.
     
    tswalk likes this.
  39. robertwahler

    robertwahler

    Joined:
    Mar 6, 2013
    Posts:
    43
    I think the root problem is really that the entire binder class is ignored in Unity (NET35). The binder class includes BindToName and BindToType.

    The code for SerializationBinder is available here. It has the abstract BindToType definition. I'm not sure why it can't also be used for Unity (NET35).

    Code (CSharp):
    1. #if (DOTNET || PORTABLE40 || PORTABLE)
    2. using System;
    3. using System.Reflection;
    4. using Newtonsoft.Json.Shims;
    5.  
    6. namespace Newtonsoft.Json
    7. {
    8.     /// <summary>
    9.     /// Allows users to control class loading and mandate what class to load.
    10.     /// </summary>
    11.     [Preserve]
    12.     public abstract class SerializationBinder
    13.     {
    14.         /// <summary>
    15.         /// When overridden in a derived class, controls the binding of a serialized object to a type.
    16.         /// </summary>
    17.         /// <param name="assemblyName">Specifies the <see cref="Assembly"/> name of the serialized object.</param>
    18.         /// <param name="typeName">Specifies the <see cref="System.Type"/> name of the serialized object</param>
    19.         /// <returns>The type of the object the formatter creates a new instance of.</returns>
    20.         public abstract Type BindToType(string assemblyName, string typeName);
    21.  
    22.         /// <summary>
    23.         /// When overridden in a derived class, controls the binding of a serialized object to a type.
    24.         /// </summary>
    25.         /// <param name="serializedType">The type of the object the formatter creates a new instance of.</param>
    26.         /// <param name="assemblyName">Specifies the <see cref="Assembly"/> name of the serialized object.</param>
    27.         /// <param name="typeName">Specifies the <see cref="System.Type"/> name of the serialized object.</param>
    28.         public virtual void BindToName(Type serializedType, out string assemblyName, out string typeName)
    29.         {
    30.             assemblyName = null;
    31.             typeName = null;
    32.         }
    33.     }
    34. }
    35.  
    36. #endif
    I may be over simplifying, but if an end user is willing to define SerializationBinder then it should not be ignored as it is now. A simple runtime check when the SerializationBinder is applied to make sure it isn't null and defines the proper methods should remove the need for any compiler defines. That would allow backward compatibility. I think :)
     
  40. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Right, that's a shim to make it available for UWP / 8.1 otherwise it's not available at all. It doesn't get ignored completely for .NET 3.5, only during deserialization because BindToName doesn't exist. For the portable targets this is simple because the serialization binder just gets created from scratch as you see above. For .NET 3.5 it's not that simple because it's a built in type and I can't just add a virtual method to it.

    I could check for the existence of the method but it would require an extra reflection call for every type resolution which isn't good. I could create an interface.. ITypeNameBindable and add the BindToName and then check for that... you could use that interface on your custom binders and then I could just do:

    Code (csharp):
    1.  
    2. if(serializer._binder is ITypeNameBindable)
    3. {
    4.     //Execute BindToName
    5. }
    6.  
    And I could limit that interface so that it doesn't output that method signature for portable and that might work...
     
  41. robertwahler

    robertwahler

    Joined:
    Mar 6, 2013
    Posts:
    43
    Sorry. I meant that the binder is ignored completely due to compiler defines. This is from ReflectionUtils.cs. I see that you'd need another reflection call to see if BindToName exists or you could remove the NET35 exclusion and define a default BindToName implementation for NET35.

    Code (CSharp):
    1. public static string GetTypeName(Type t, FormatterAssemblyStyle assemblyFormat, SerializationBinder binder)
    2.         {
    3.             string fullyQualifiedTypeName;
    4. #if !(NET20 || NET35)
    5.             if (binder != null)
    6.             {
    7.                 string assemblyName, typeName;
    8.                 binder.BindToName(t, out assemblyName, out typeName);
    9.                 fullyQualifiedTypeName = typeName + (assemblyName == null ? "" : ", " + assemblyName);
    10.             }
    11.             else
    12.             {
    13.                 fullyQualifiedTypeName = t.AssemblyQualifiedName;
    14.             }
    15. #else
    16.             fullyQualifiedTypeName = t.AssemblyQualifiedName;
    17. #endif
    18.  
    19.             switch (assemblyFormat)
    20.             {
    21.                 case FormatterAssemblyStyle.Simple:
    22.                     return RemoveAssemblyDetails(fullyQualifiedTypeName);
    23.                 case FormatterAssemblyStyle.Full:
    24.                     return fullyQualifiedTypeName;
    25.                 default:
    26.                     throw new ArgumentOutOfRangeException();
    27.             }
    28.         }

    I would we happy for any solution that doesn't cause you a maintenance headache. Thanks!
     
  42. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Robert - Do me a favor and send me an email - dustin (at) parentelement.com

    I'll have an update for you to test in about 15 minutes.
     
  43. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Ok, I have an update ready to send you as soon as you send me an email. Here's what I did...

    In Newtonsof.Json.Shims I added an interface called IBindToName. That interface is available for all platforms but the BindToName define is only for .NET 3.5. It looks like this:

    Code (csharp):
    1.  
    2. using System;
    3.  
    4. namespace Newtonsoft.Json.Shims
    5. {
    6.     public interface IBindToName
    7.     {
    8. #if NET35
    9.         void BindToName(Type serializedType, out string assemblyName, out string typeName);
    10. #endif
    11.     }
    12. }
    13.  
    Next I updated DefaultSerializationBinder to implement IBindToName. This meant that I had to change BindToName so that in .NET 3.5 it uses the interface implementation, otherwise it overrides from the base. You'll need to replicate this in your own serialization binder(s) as well. I did it in the default just to show an example. It looks like this:

    Code (csharp):
    1.  
    2. #if (NET35)
    3.         public virtual void BindToName(Type serializedType, out string assemblyName, out string typeName)
    4. #else
    5.         public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
    6. #endif
    7.         {
    8. #if (DOTNET || PORTABLE)
    9.             assemblyName = serializedType.GetTypeInfo().Assembly.FullName;
    10.             typeName = serializedType.FullName;
    11. #else
    12.             assemblyName = serializedType.Assembly.FullName;
    13.             typeName = serializedType.FullName;
    14. #endif
    15.         }
    16.  
    So now that BindToName method is always available for the default binder. This simplifies things... since I marked it virtual in the DefaultSerializationBinder you can inherit from DefaultSerializationBinder instead of the base SerializationBinder and you don't need to use your own defines... you simply "override" and it will be available in all cases.

    And finally, I rewrote the type resolution portion of GetTypeName so in .NET 3.5 it checks for the IBindToName implementation... casts if it exsts and calls BindToName, otherwise falls back to the original behavior. In the case of .NET 4+ (i.e. UWP) the old behavior is retained... it now looks like this:

    Code (csharp):
    1.  
    2.             if (binder != null)
    3.             {
    4. #if (NET35)
    5.                 if (binder is IBindToName)
    6.                 {
    7.                     string assemblyName, typeName;
    8.                     (binder as IBindToName).BindToName(t, out assemblyName, out typeName);
    9.                     fullyQualifiedTypeName = typeName + (assemblyName == null ? "" : ", " + assemblyName);
    10.                 }
    11.                 else
    12.                 {
    13.                     fullyQualifiedTypeName = t.AssemblyQualifiedName;
    14.                 }
    15.  
    16. #else
    17.                 string assemblyName, typeName;
    18.                 binder.BindToName(t, out assemblyName, out typeName);
    19.                 fullyQualifiedTypeName = typeName + (assemblyName == null ? "" : ", " + assemblyName);
    20. #endif
    21.             }
    22.             else
    23.             {
    24.                 fullyQualifiedTypeName = t.AssemblyQualifiedName;
    25.             }
    26.  
    That should solve your issue without introducing any breaking changes that I can think of. I'm exporting a new package now so I'll have it ready to send you. The package will include the changes for 2.0.1 which is pending asset store approval and these new changes will appear on 2.0.2 but I don't have immediate release plans for it until additional changes are ready as well.
     
  44. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Oh and in addition to the above...

    If you inherit from SerializationBinder you'll need to implement IBindToName yourself. If you inherit from DefaultSerializationBinder instead, then you get it for free plus the UWP support so I would highly recommend the latter.

    However, also see my replies to tswalk above... Since in Portable the reference switches to the Json .Net provided SerializationBinder class, for some reason Unity's reference rewriter isn't picking up on that so if you want to target Portable (UWP or 8.1 Universal), for now you'll have to put your custom binders into separate DLLs, one targeting .NET 4.5 and a Portable Class Library... it's Profile259... and the other a regular class library targetting .NET 3.5. In the UWP DLL, just choose the Portable (Windows, Windows Phone, iOS, Android) or something like that... it's the Xamarin compatible one. Then in the project properties change the device targets so that the Windows target goes to Windows 8 instead of 8.1 and leave everything else. That'll get you the correct PCL profile target.
     
  45. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    UPDATE
    Version 2.0.1 has been accepted and is now live on the asset store.
     
  46. robertwahler

    robertwahler

    Joined:
    Mar 6, 2013
    Posts:
    43
    The update works great! Thanks for the fabulous support Dustin. Thanks also for picking up on my flub of the deserializer call parameters. I've updated the gist for posterity with the correct parameters.

    https://gist.github.com/robertwahler/b3edba62493672c058955bbce07d5790

    Thanks again!
     
    Dustin-Horne likes this.
  47. tswalk

    tswalk

    Joined:
    Jul 27, 2013
    Posts:
    1,109
    I got it,.. taking a look at it now.
     
    Dustin-Horne likes this.
  48. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Also as an FYI - Version 2.0.1 is now available to all of those who purchased through FastSpring! My apologies on the delay. When I originally built the site I made one major oversight and limited the licenses to Major Version. I've erased that limitation for now as I will not be charging for upgrades of Json .Net for Unity any time in the near future.

    I'm working on an entirely new site as well. Right now the 2.0.1 package is only the version published from Unity 5.3.5, but I will be updating my product configuration to support multiple download options for a single product.
     
  49. tswalk

    tswalk

    Joined:
    Jul 27, 2013
    Posts:
    1,109
    good news, I was able to finally get it working with .NET scripting backend and Universal 10, I sent you an email with some samples.

    I ended up having to move more than just the DefaultSerializationBinder into the assembly, I also ended up creating a new 'Binder' like class that declares the custom parameters for JsonSerializerSettings. For whatever reason, Unity would still cling onto the "System.Runtime.Serialization" namespace.

    Knowing now how to make this work, i'll refactor a lot to have a better naming scheme, and probably make all of my class that inherit from DefaultSerializationBinder internal to the assembly, exposed only by a static class... would be clean.
     
    Last edited: Jun 28, 2016
  50. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Thanks Troy! Let me know if you hear any movement on that bug. Im going to file a similar bug with a small repro project.
     
    tswalk likes this.