Deserialization not working with JSON.NET on iOS device (iOS simulator works)

TedRogersTedRogers USMember ✭✭✭✭

I have pretty much completed by app using Xamarin.Android and am now building up the iOS version. I use JSON.NET in my business layer to deserialize JSON from web requests. Works great on Android and the iOS simulator but does not work on my iPhone. I get the problem when I execute the exact same code as I use for Android or run on the iOS simulator.

At first I was getting the "Unable to find a constructor to use for type..." exception. I found if I did a new on the object first, I wouldn't get that error but the deserialization didn't work. This seems like an obvious clue that something is messed up. I've spent enough time on this now that it must be something really stupid. (on my part!)

Here is the test code I am using and the class I am deserializing. Any help would be greatly appreciated as I don't want to have to pluck the properties out one by one.

using System;

namespace TestUserNS
{
    public class TestUser
    {
        public TestUser ()
        {
        }
        public string name { get; set; }
        public int id { get; set; }
        public string number { get; set; }
    }
}

private void TestDeserialize()
{
    try {
        string json = "{\"status\":\"SUCCESS\",\"user\":{\"id\":185,\"name\":\"Joe S\",\"number\":\"15555555555\"}}";
        JObject jsonResponset = JObject.Parse (json);
        JObject user_json = (JObject)jsonResponset ["user"];
        if (user_json != null) {
            Console.WriteLine("json = {0}", user_json.ToString());
            TestUserNS.TestUser testUserTemp = new TestUserNS.TestUser();
            TestUserNS.TestUser testUser = user_json.ToObject<TestUserNS.TestUser> ();
            Console.WriteLine ("id = {0} name = {1} phone = {2}", testUser.id, testUser.name, testUser.number);
            // Output: id = 0 name =  phone = 
            int id = (int)user_json["id"];
            string name = (string)user_json["name"];
            string number = (string)user_json["number"];
            Console.WriteLine ("id = {0} name = {1} phone = {2}", id, name, number);
            // Output: id = 185 name = Joe S phone = 15555555555
        }
    } catch (Exception e) {
        Console.WriteLine ("e = {0}", e.Message);
    }
}

Posts

  • SKallSKall USMember ✭✭✭✭
    edited March 2014

    If it runs on simulator but not on device, have you checked that all references are built for both targets? If yes, have you tried changing compiler settings? Maybe something is left out when building for the device. Are you deploying from Mac or from Windows (Visual Studio)?

    Any particular reason why you are using JObject instead of deserializing to a TestUser? I created a generic response class which should do the trick with JsonConvert (notice the 'user' has been changed to generic 'value'):

    public class TestUser
    {
        public const string DummyResponse = 
            "{\"status\":\"SUCCESS\",\"value\":{\"id\":185,\"name\":\"Joe S\",\"number\":\"15555555555\"}}";
    
        public string Name { get; set; }
        public int Id { get; set; }
        public string Number { get; set; }
    }
    
    public class ServiceResponse<T>
    {
        public string Status { get; set; }
    
        public T Value { get; set; }
    }
    

    Using this it returned correct response with both simulator and actual iOS device:

            var serializer = new SimplyMobile.Text.JsonNet.JsonSerializer ();
    
            var userResponse = serializer.Deserialize<ServiceResponse<TestUser>> (
                TestUser.DummyResponse);
    
            if (userResponse.Status == "SUCCESS")
            {
                System.Diagnostics.Debug.WriteLine (userResponse.Value.Id);
                System.Diagnostics.Debug.WriteLine (userResponse.Value.Name);
                System.Diagnostics.Debug.WriteLine (userResponse.Value.Number);
            }
    

    The serializer is simply a wrapper to JsonConvert class (so I can change JSON serializers on the fly if I want to):

        public T Deserialize<T>(string data)
        {
            return JsonConvert.DeserializeObject<T>(data);
        }
    
  • TedRogersTedRogers USMember ✭✭✭✭

    @SKall‌ - thanks for the response. It ended up being my linker options. I had enabled "Link All Assemblies" in a desperate attempt to get debugging on my iPhone to work more reliably. I don't quite understand why "Link All Assemblies" causes this problem but there was a yellow warning icon in the linker options that I overlooked. In the end, I had to switch to WiFi debugging the get any sort of debugging reliability but didn't think to go back and change that option. I knew it had to be something stupid.

    Regarding your question about deserializing to the JObject. The code I provided isn't really the structure of the code I am using. The deserialization to the JObject is done separately from the code that knows what object should be found in the JSON. That being said I really like your solution above and will look into doing something similar.

    Thanks for your help!

    Ted

  • TedRogersTedRogers USMember ✭✭✭✭

    @SKall‌ I wired in your approach to deserialization but ran into a problem. In the example, you changed the name of "user" property to "value" in the response string. I can't do that, so is there a way to declare ServiceResponse so that the property name matches the response string I am receiving so it will deserialize properly?

    I really like your approach but can't quite sort out how to use it because of this.

    Thanks.

    Ted

  • SKallSKall USMember ✭✭✭✭
    edited March 2014

    Change the response to non-generic type:

    public class TestUserResponse
    {
        public string Status { get; set; }
    
        public TestUser User { get; set; }
    }
    

    If the responses are always same format then just use a base class to inherit from.

    public class Response
    {
        public string Status { get; set; }
    }
    
    public class TestUserResponse : Response
    {
        public TestUser User { get; set; }
    }
    
  • TedRogersTedRogers USMember ✭✭✭✭

    @T.J.Purtell.1752 - you keep coming up with helpful answers. Thanks again.

    So, the primary reason to use "Link All Assemblies" is to reduce binary size?

    Ted

  • SebastienPouliotSebastienPouliot CAXamarin Team Xamurai

    So, the primary reason to use "Link All Assemblies" is to reduce binary size?

    Yes. It both reduce (size) and optimize (binding code) the application, not just the SDK code that Xamarin ships to you (which is what the default Link SDK does).

    There was a presentation at Evolve (2013) about what it mean (in details). You can watch the recording from here.

  • TedRogersTedRogers USMember ✭✭✭✭

    @SebastienPouliot‌ - thanks. The description in the UI is not very enlightening.

  • PhilPorterPhilPorter USMember, University

    @T.J.Purtell.1752 Thanks, your answer help me also.

  • JoaquinGrechJoaquinGrech ESMember ✭✭

    wow, half a day lost on this... thanks for the answer. Everything I was finding on google was useless.

  • jucaripojucaripo MXMember

    Thank you, your contribution helped a lot in my project.

  • RaymondKellyRaymondKelly USMember ✭✭✭

    Thank for the input here folks and I have verified the same that "Link SDK assemblies only" fixes the issue. But is there a way to make this work properly with "Link all assemblies" (my binary went from 9 meg to 17)? On top of that, I am inside a PCL that utilizes json.net, so I dont think the [Preserve] attribute will work in this case. Thanks.

  • rmaciasrmacias USBeta, University ✭✭✭✭✭

    @RaymondKelly, you can use the Preserve attribute inside of a PCL, but you will have to define it yourself somewhere inside of your PCL:

    public sealed class PreserveAttribute : System.Attribute 
    {
        public bool AllMembers;
        public bool Conditional;
    }
    

    The namespace doesn't matter, only the class name. The linker will honor the Preserve Attribute no matter where it's defined.

    See here for more info.

  • edwardrvargasedwardrvargas USMember

    Hi. I am using Jason.net in my current cross platform but I am having problems with the iOS app. Because when I generate the IPA file an error is generated that gets attached to the device. I am using --linkskip=NewtonSoft.Json to preserve the DLL but without a positive result.

  • Thanks @T.J.Purtell.1752 it works

  • @T.J.Purtell.1752 Thank you. I spend several hours trying to solve this

  • sJiasJia USMember

    @T.J.Purtell.1752 Just want to say thank you.

  • LakshyaLakshya Member ✭✭✭

    @T.J.Purtell.1752 Where I add this..

Sign In or Register to comment.