Search Unity

Dealing With JSON Object Of Dynamic Type

Discussion in 'Scripting' started by Nigey, Feb 15, 2018.

  1. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    Hi Guys,

    Been going nuts all morning. Simple simple simple problem. I have a json string that I want to deserialize into a class. The problem is it won't deserialize, as one field is of type 'object'. Json is being retreived by an API outside of my control, so I can't change how it's posted to me.

    This is included in the total thing:

    Code (CSharp):
    1. "preferences":[{"name":"auth_manual_passwordupdatetime","value":"1511800938"},{"name":"block_myoverview_last_tab","value":"courses"},{"name":"email_bounce_count","value":"1"},{"name":"email_send_count","value":"5"},{"name":"login_failed_count_since_success","value":"4"},{"name":"theme_adaptable_full","value":"fullin"},{"name":"theme_adaptable_zoom","value":"nozoom"},{"name":"_lastloaded","value":1518699801}]}]
    2.  
    This last bit
    is what breaks everything, including JSONUtility, JSONObject, ect.

    What's the best way to auto format this? I was thinking of regular expressions, but it just feels to dirty. Any thoughts guys?

    Thanks!
     
  2. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,188
    Is the json string exactly what you get back. Copied exactly and you didn't leave anything out?

    The values aren't the issue if that is the case, however, you've got some odd brackets and appear to be missing some. For example.

    Code (CSharp):
    1. {"preferences":[{"name":"auth_manual_passwordupdatetime","value":"1511800938"},{"name":"block_myoverview_last_tab","value":"courses"},{"name":"email_bounce_count","value":"1"},{"name":"email_send_count","value":"5"},{"name":"login_failed_count_since_success","value":"4"},{"name":"theme_adaptable_full","value":"fullin"},{"name":"theme_adaptable_zoom","value":"nozoom"},{"name":"_lastloaded","value":1518699801}]}
    This is valid. Now, json.net on the asset store will handle this just fine, you could set "value" up as an object type. However, what I would do is just set it as a string type. It should convert the int into a string. Then if you need it as an int, you could use int.tryParse to see if it can be converted to an int and handle it as you need to.

    Added tip: You can paste any json string into http://json2csharp.com/ and it will give you what values to setup and tell you if it's a valid json. This works well with json.net.
     
    ugurcan3238 and Nigey like this.
  3. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    Yeah that's exactly what I did! and it seemed to like my output. It came out with this:

    Code (CSharp):
    1. public class Customfield
    2. {
    3.     public string type { get; set; }
    4.     public string value { get; set; }
    5.     public string name { get; set; }
    6.     public string shortname { get; set; }
    7. }
    8.  
    9. public class Preference
    10. {
    11.     public string name { get; set; }
    12.     public object value { get; set; }
    13. }
    14.  
    15. public class RootObject
    16. {
    17.     public int id { get; set; }
    18.     public string username { get; set; }
    19.     public string firstname { get; set; }
    20.     public string lastname { get; set; }
    21.     public string fullname { get; set; }
    22.     public string email { get; set; }
    23.     public string department { get; set; }
    24.     public int firstaccess { get; set; }
    25.     public int lastaccess { get; set; }
    26.     public string auth { get; set; }
    27.     public bool suspended { get; set; }
    28.     public bool confirmed { get; set; }
    29.     public string lang { get; set; }
    30.     public string theme { get; set; }
    31.     public string timezone { get; set; }
    32.     public int mailformat { get; set; }
    33.     public string description { get; set; }
    34.     public int descriptionformat { get; set; }
    35.     public string country { get; set; }
    36.     public string profileimageurlsmall { get; set; }
    37.     public string profileimageurl { get; set; }
    38.     public List<Customfield> customfields { get; set; }
    39.     public List<Preference> preferences { get; set; }
    40. }
    which I converted to this:

    Code (CSharp):
    1. namespace ELearning
    2. {
    3.     using System;
    4.     using System.Collections.Generic;
    5.     using UnityEngine;
    6.  
    7.     [Serializable]
    8.     public class Customfield
    9.     {
    10.         [SerializeField] private string type;
    11.         [SerializeField] private string value;
    12.         [SerializeField] private string name;
    13.         [SerializeField] private string shortname;
    14.     }
    15.  
    16.     [Serializable]
    17.     public class Preference
    18.     {
    19.         [SerializeField] private string name;
    20.         [SerializeField] private object value;
    21.     }
    22.  
    23.     [Serializable]
    24.     public class User
    25.     {
    26.         [SerializeField] private int id;
    27.         [SerializeField] private string username;
    28.         [SerializeField] private string firstname;
    29.         [SerializeField] private string lastname;
    30.         [SerializeField] private string fullname;
    31.         [SerializeField] private string email;
    32.         [SerializeField] private string department;
    33.         [SerializeField] private int firstaccess;
    34.         [SerializeField] private int lastaccess;
    35.         [SerializeField] private string auth;
    36.         [SerializeField] private bool suspended;
    37.         [SerializeField] private bool confirmed;
    38.         [SerializeField] private string lang;
    39.         [SerializeField] private string theme;
    40.         [SerializeField] private string timezone;
    41.         [SerializeField] private int mailformat;
    42.         [SerializeField] private string description;
    43.         [SerializeField] private int descriptionformat;
    44.         [SerializeField] private string country;
    45.         [SerializeField] private string profileimageurlsmall;
    46.         [SerializeField] private string profileimageurl;
    47.         [SerializeField] private List<Customfield> customfields;
    48.         [SerializeField] private List<Preference> preferences;
    But it would throw out the: "JSON must represent an object type" all the time. So it accepts 'object' as a type and will automatically work with that, or cast to string?

    This is the exact output of the json (though I've removed my login details for privacy:

    Code (CSharp):
    1. [
    2.   {
    3.     "id": 6,
    4.     "username": "nigel",
    5.     "firstname": "Nigel",
    6.     "lastname": "",
    7.     "fullname": "Nigel",
    8.     "email": "nigel_.com",
    9.     "department": "",
    10.     "firstaccess": 1,
    11.     "lastaccess": 1,
    12.     "auth": "manual",
    13.     "suspended": false,
    14.     "confirmed": true,
    15.     "lang": "en",
    16.     "theme": "",
    17.     "timezone": "99",
    18.     "mailformat": 1,
    19.     "description": "",
    20.     "descriptionformat": 1,
    21.     "country": "GB",
    22.     "profileimageurlsmall": "http://www..co.uk/ecademy//image.php//core//u/f2",
    23.     "profileimageurl": "http://www..co.uk//theme/.php//core/1516033544/u/f1",
    24.     "customfields": [
    25.       {
    26.         "type": "checkbox",
    27.         "value": "0",
    28.         "name": "Food Allergies",
    29.         "shortname": "FoodAllergies"
    30.       }
    31.     ],
    32.     "preferences": [
    33.       {
    34.         "name": "auth_manual_passwordupdatetime",
    35.         "value": "1511800938"
    36.       },
    37.       {
    38.         "name": "block_myoverview_last_tab",
    39.         "value": "courses"
    40.       },
    41.       {
    42.         "name": "email_bounce_count",
    43.         "value": "1"
    44.       },
    45.       {
    46.         "name": "email_send_count",
    47.         "value": "5"
    48.       },
    49.       {
    50.         "name": "login_failed_count_since_success",
    51.         "value": "4"
    52.       },
    53.       {
    54.         "name": "theme_adaptable_full",
    55.         "value": "fullin"
    56.       },
    57.       {
    58.         "name": "theme_adaptable_zoom",
    59.         "value": "nozoom"
    60.       },
    61.       {
    62.         "name": "_lastloaded",
    63.         "value": 1518708035
    64.       }
    65.     ]
    66.   }
    67. ]
    Lastly you say json.net will get that working. Do you know whether the built in Unity JsonUtility covers it?
     
    Last edited: Feb 15, 2018
  4. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,188
    Right, I suggest you switch to json.net, which uses properties instead of fields, but tends to be better when you have situations like this and can't control the json string you get. It's free on the asset store.

    But, like I mentioned, if you are getting ints and strings for "value", you should be able to set that as a string type in the class and any ints should be converted to strings (at least they do for me using json.net, jsonutility and others may not handle it the same)
     
    Nigey likes this.
  5. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    Right, limited JSONUtility it is. I can confirm it's 'object' field which is the issue by the way. This hack fixes it:

    Code (CSharp):
    1. protected string JsonRemoveJSObjects(string input)
    2.         {
    3.             // Most hacky hack there was ever to hack. Never show this to anyone.... lol
    4.             int index = input.IndexOf("_lastloaded");
    5.  
    6.             if(index != -1)
    7.             {
    8.                 string output = input.Substring(1, index - 1);
    9.                 output += "_lastloaded\",\"value\":\"IGNORE_THIS\"}]}";
    10.                 return input;
    11.             }
    12.             return input;
    13.         }
    I'll check out json.net. Thanks!

    Okay, for anyone else checking this out. Do one of the following:

    1) Use json.net and copy from the website.

    2) Create an ISerializationCallbackReceiver for System.object. The beenefit being you don't need an extra third party dependency and don't need to break SOLID principles of encapsulation to satisfy the utility.
     
    Last edited: Feb 15, 2018