1
\$\begingroup\$

I currently have a JSON file:

{
    "locations": [
        {
            "name": "Mars",
            "type": "mining",
            "metals": 500,
            "gas": 0
        },
        {
            "name": "Jupiter",
            "type": "gas",
            "metals": 0,
            "gas": 1000
        }
    ]                   
}

And a script that reads it:

public class JSONReader : MonoBehaviour
{
    public TextAsset textJSON;

    [Serializable]
    public class Location
    {
        public string name;
        public string type;
        public int metals;
        public int gas;
    }

    [Serializable]
    public class LocationArray
    {
        public Location[] locations;  
    }

    public LocationArray mLocationArray = new LocationArray(); 
    public Location[] locationArray;

    void Start()
    {
        mLocationArray = JsonUtility.FromJson<LocationArray>(textJSON.text);
        locationArray = mLocationArray.locations;

        foreach (Location loc in locationArray)
        {
            Debug.Log(loc.name);
        }

I'm intending to modify the JSON file to add more locations and also nest the planets in star systems, however I'm not quite sure how to modify my script to accommodate the nesting. I've read that JSONUtility cannot output 2-dimensional arrays, however is that the only way that nested JSON data can be handled? Do I absolutely need to use a third-party library for this?

\$\endgroup\$
3
  • \$\begingroup\$ Isnt this just adding a parent (starsystem), that contains locations? \$\endgroup\$
    – Zibelas
    Aug 31, 2023 at 16:55
  • \$\begingroup\$ It's not clear to me what specific trouble you've had adding an additional layer of nesting. You already nest a name inside a location inside an array of locations. Where have you had trouble continuing to nest as needed, following this pattern? \$\endgroup\$
    – DMGregory
    Aug 31, 2023 at 17:16
  • 1
    \$\begingroup\$ I recommend you use Newtonsoft instead of JsonUtility: docs.unity3d.com/Packages/[email protected]/… \$\endgroup\$
    – Evorlor
    Aug 31, 2023 at 22:38

1 Answer 1

1
\$\begingroup\$

As @Evorlor mentioned in comments, you should using Newtonsoft Json (also known as Json.Net)

https://docs.unity3d.com/Packages/[email protected]/manual/index.html

It's the go-to default everywhere C# is used, it's cross-platform, very flexible and incredibly lightweight.

Once you've got that installed, all you need to do is define an object that has the same structure as your Json.

So... If you hypothetically wanted to support this Json:

[
    {
        "name": "Sol",
        "type": "star",
        "metals": 0,
        "gas": 0,
        "locations": [
            {
                "name": "Mars",
                "type": "mining",
                "metals": 500,
                "gas": 0
            },
            {
                "name": "Jupiter",
                "type": "gas",
                "metals": 0,
                "gas": 1000,
                "locations": [
                    {
                        "name": "Europa",
                        "type": "moon",
                        "metals": 0,
                        "gas": 0
                    },
                }
            }
        ]
    },
    {
        "name": "Proxima Centauri",
        "type": "star",
        "metals": 0,
        "gas": 0,
        "locations": [
            {
                "name": "Proxima Centauri C",
                "type": "mining",
                "metals": 500,
                "gas": 0
            }
        ]
    }
]

You could use a class structure like this:

public class Location
{
    public string name { get; set; }
    public string type { get; set; }
    public int metals { get; set; }
    public int gas { get; set; }

    public List<Location> locations { get; set; }
}

And if your json is in a string jsonConfig...

using Newtonsoft.Json;
...
var rootLocationList = JsonConvert.DeserializeObject<List<Location>>(jsonConfig);

rootLocationList is now a List<Location>

If a given location has child locations, there will be a populated list at locations, otherwise locations will be null.

Any location can have sub-locations, so you have almost¹ as many levels of nesting as you wish.

Note that I've changed the root of your Json to a list of locations. If you want other properties besides a list of locations, create a root object with a List<Location> locations property and whatever other properties you need).

You can also map using different names in the Json, should you wish:

public class Location
{
    ...
    [JsonProperty("locations")]
    public List<Location> childLocations { get; set; }
}

Check out the other parameters on the JsonProperty attribute to get an idea of what's possible.

¹ There's a default limit of 64 levels deep, but it's configurable.

\$\endgroup\$
6
  • \$\begingroup\$ Thanks! This works when I keep rootLocationList as a local variable, but as soon as I try to turn it into a publicly-accessible field (i.e. public List<LocationSO> rootLocationList = new List<LocationSO>(); , Unity throws a warning: Serialization depth limit 10 exceeded at 'LocationSO.locations'. There may be an object composition cycle in one or more of your serialized classes. Am I doing something wrong? \$\endgroup\$
    – misaochan
    Sep 8, 2023 at 8:51
  • 1
    \$\begingroup\$ You're dealing with conflicting serialisation strategies. Unity regularly serialises everything in a scene/attached to game objects (basically anything you wire up in the editor). It does this when saving/loading/playing/switching scenes, editing prefabs and a lot of other scenarios, and it does it using Unity's built in serializer (into a binary format for efficiency). When you expose the localRootLocationList in the editor, Unity sees the property and tries to serialise it. \$\endgroup\$
    – Basic
    Sep 8, 2023 at 12:08
  • 1
    \$\begingroup\$ I suspect unity's built-in serializer recursion limit of 10 is configurable, but have never had to change it myself, so you would probably benefit by posting that as a new question. The key detail is ... It's nothing to do with loading from JSON. If you had that same property and manually added a tree nesting 11 levels deep, you'd get the same issue. \$\endgroup\$
    – Basic
    Sep 8, 2023 at 12:10
  • 1
    \$\begingroup\$ If you don't want Unity serializing that field (you'll always load the value yourself from Json any time the scene/object is reloaded) and don't mind it not showing in the editor, you can change the field to a property ... public List<LocationSO> rootLocationList {get; set;} which you can reference in code, but not the editor. [In hindsight, this should've been an edit, not 3 comments... Sorry] \$\endgroup\$
    – Basic
    Sep 8, 2023 at 12:14
  • \$\begingroup\$ Hey, thanks a lot for the clarification! For some reason changing it to a property didn't solve the issue for me (still got the same error), but using [NonSerialized] to make it not serializable worked. :) Just checking: The only downside of this approach is that I can't change the values in the editor, right? \$\endgroup\$
    – misaochan
    Sep 8, 2023 at 13:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .