List only fills after playmode

Hello, I have got a problem that i have been working on for a while but I can’t figur it out. I’m not a good programmer, so please bear with me. I have a .Json file that is my database and a class which models the database and i put that content in a list. Afterwards I want to use that list in a different class. If I try to use it in said class the new list is emtpy, but if I run playmode the list is full in the editor window.
What I know so far is, that the list in the jsonController class is filled but I guess its call after the shopManager class ran through, which is a problem. Additionally, if I don’t the use the null check I get a argument out of range exception. I guess it’s happening because the list is empty.

Here’s my code:

using System.Collections.Generic;
using System;
using UnityEngine;


[Serializable]
public class jsonDataClass 
{
    public string Databasename;
    public List<itemStatsList> itemStats;

}

[Serializable] 
public class itemStatsList 
{
    public int objectID;
    public string objectSlug;
    public string itemName;
    public int itemPrice;
    public string itemDescription;
    public string itemURL;
    public int Height;
    public int Tiefe;
    public int Gewicht;

    public itemStatsList( int objectID, string objectSlug, string itemName, int itemPrice, string itemDescription, string itemURL, int Height, int Tiefe, int Gewicht)
    {
        this.objectID = objectID;
        this.objectSlug = objectSlug;
        this.itemName = itemName;
        this.itemPrice = itemPrice;
        this.itemDescription = itemDescription;
        this.itemURL = itemURL;
        this.Height = Height;
        this.Tiefe = Tiefe;
        this.Gewicht = Gewicht;

    }



}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class jsonController: MonoBehaviour {

   
    public string jsonURL; 
    public static jsonController Instance { get; private set; }
    public static List<itemStatsList> shopItems = new List<itemStatsList>();
    public jsonDataClass jsnData { get; set; }



    public List<itemStatsList> ShopItems()
    {

        return shopItems;
    }


    private void Awake()
    {
        Instance = this;
        
        StartCoroutine(getData());





    

        IEnumerator getData()
        {
            Debug.Log("wird geladne");

            WWW _www = new WWW(jsonURL);
            yield return _www;
            if (_www.error == null)
            {


                processJsonData(_www.text);

            }
            else
            {
                Debug.Log("URL nicht geladen");

            }
        }
 

         void processJsonData(string _url)
        {
            jsnData = JsonUtility.FromJson<jsonDataClass>(_url);


            //Debug.Log(jsnData.Databasename);
            //Debug.Log(jsnData.itemStats);



            foreach (itemStatsList x in jsnData.itemStats)
            {
                shopItems.Add(new itemStatsList(x.objectID, x.objectSlug, x.itemName, x.itemPrice, x.itemDescription, x.itemURL, x.Height, x.Tiefe, x.Gewicht));
            


                //Debug.Log(x.objectID);
                //Debug.Log(x.itemURL);
            }



       


        }


    }

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class ShopItemManger : MonoBehaviour
{
    public jsonController myjsonController;
    public List<itemStatsList> shopItems2;
    public GameObject[] shopObjekt;


    private void Awake()
    {
        
     
    }


    // Start is called before the first frame update
    void Start()
    {
        myjsonController = new jsonController();
        myjsonController = GameObject.Find("Json Controller").GetComponent<jsonController>();
        shopItems2 = new List<itemStatsList>();
        shopItems2 = myjsonController.ShopItems();

      





        for (int i = 0; i < shopObjekt.Length; i++)
        {

            
            Debug.Log("Player Number " + i + " is named " + shopObjekt*.name);*

}

//Debug.Log("bis hier " + myjsonController.ShopItems());

if (shopItems2 != null && shopItems2.Any())
{
Debug.Log("bis hier " + shopItems2[0].objectID);
}

Debug.Log("In der list ist noch nichts drin " );
// Debug.Log("In der list ist noch nichts drin "+ jsonController.shopItems[0].objectID);

}
Here’s a Screenshot of what it looks like in unity: https://ibb.co/RYf0fsw
(Sorry, it wouldn’t load the picture)
I’m sry some of the Debug logs are in German, but I hope you can understand whats going on anyway.
I appreciate you guys taking your time and am grateful for any help.

Well what you have here is a classical very obvious “race condition”. Even it’s not a race condition as if you have competing threads, it’s a matter of event ordering. Yes Awake is executed before Start. However your Awake method starts a coroutine which does perform a HTTP / HTTPS request in the background. So once the coroutine is started it will take some time until the request actually finishes. Probably several frames. However the Start method where you try to read the data runs immediately after the Awake methods have been called. So it’s actually impossible for the data to have arrived already. Even if the request was that quick coroutines are resumed on the main thread. So it’s not possible to have the coroutine finish its work before the Start method is executed.

Any kind of webrequest in general takes time. So you should work with either events (which would be the best solution) or with state variables. Events are generally better because they don’t require a script to constantly polling the state of the download / parsing.

I want to add a few things you really should change here. Specifically this line should be removed for two reasons:

myjsonController = new jsonController();

First of all your jsonController class is a MonoBehaviour which you can not create with “new”. (well technically you can but you will get a warning in the console and the class also doesn’t work in regards to Unity). After you assigned your newly created instance to the myjsonController variable you immediately replace that content by what the GetComponent method returns which of course is your actual instance in the scene. The same thing holds true for the next two lines. This line also should be removed:

shopItems2 = new List<itemStatsList>();

as you create a new list which is immediately replaced by what your “ShopItems()” method returns.

Your seperation of concerns seems a bit weird to me. Why did you outsource the loading of the data into a seperate class, especially since your actual data structures which you defined alongside your jsonController are directly used by your ShopItemManger (while we are at that point I highly recommend you find a consistent casing style. Class names and method names in C# generally start with a capital letter).

Back to the actual issue:
To solve your issue there are generally different approaches. If you only want to read the content of your web resource once at the start of the game, of course it makes sense to store it in a variable once it has been received. However another approach would be every time the shop is “opened” we would read the content from the server in case something has changed. Both methods have some pros and cons. In order to run code in another class when the data actually arrived you can implement an event inside your jsonController to which your shop manager can subscribe to. So your jsonController class might just look like this:

public class jsonController: MonoBehaviour
{
    public string jsonURL; 
    public static jsonController Instance { get; private set; }
    public jsonDataClass jsnData { get; set; }
    public event System.Action OnDataReceived;
    
    public List<itemStatsList> ShopItems()
    {
        return shopItems;
    }
    private void Awake()
    {
        Instance = this;
        RefreshShopData();
    }
    public void RefreshShopData()
    {
        StartCoroutine(getData());
    }
    IEnumerator getData()
    {
        WWW _www = new WWW(jsonURL);
        yield return _www;
        if (_www.error == null)
        {
            jsnData = JsonUtility.FromJson<jsonDataClass>(_www.text);
            if (OnDataReceived != null)
                OnDataReceived();
        }
        else
        {
            Debug.Log("URL nicht geladen " + _www.error);
        }
    }
}

With those changes we can simply do this inside your ShopItemManager:

public class ShopItemManger : MonoBehaviour
{
    public List<itemStatsList> shopItems2;
    public GameObject[] shopObjekt;
    
    void Start()
    {
        jsonController.Instance.OnDataReceived.+= OnDataReceived;
    }
    void OnDataReceived()
    {
        shopItems2 = jsonController.Instance.ShopItems();
        for (int i = 0; i < shopItems2.Count; i++)
        {
            Debug.Log("Item #" + i + " ItemName: " + shopItems2*.itemName);*

}
}
}
A few things I’d like to notice here. First of all JsonUtility.FromJson does actually deserialize the incoming json text into actual object instances. So your for loop was pretty redundant as you completely duplicated the same objects manually. Since there is no actual processing that has to be done I got rid of your “processJsonData” method. However I noticed you named your parameter “url” which is completely misleading. You would just confuse yourself if you come back to this project in a month or a year. That variable / parameter should be called something like “jsonText” or just “content”.
Keep in mind that the “OnDataReceived” method is called at some time in the future when the http request has finished successfully. For a robust shop you probably want to handle the fail case event as well and do something when the request fails (i.e. if the user doesn’t have an internet connection or some years in the future when you shut down your shop server).
Currently my code loads the information once at the game start just like your original code does. As I mentioned it’s usually more common to load it “ondemand” when you open the shop or at least you usually want to provide a “refresh” button to the user. As you can see I’ve put the actual StartCoroutine call into a seperate method (RefreshShopData()) which you could call again to query the data from your server again. Of course as long as the OnDataReceived method is subscribed to the “OnDataReceived” event it will be executed again when the request finishes.
Hopefully it’s obvious that any post processing on the data you get from your server has to be done inside the OnDataReceived callback and not in Start.
ps: I don’t really have an issue with german debug messages since I’m also german ^^. Though I generally use english for everything programming related.