json file location

Hello im new to json and just learning. In my code i create a json file "i think" when i build my game. Where can i find this in my unity folder, or has it not been created till build ? im a bit confused.

        if(PlayerPrefs.GetString("highscoreTable") == null)
        {
            highscoreEntryList = new List<HighscoreEntry>()
        {
            new HighscoreEntry{score = 0, name = "???"},
        };


            highscoreEntryTransformList = new List<Transform>();
            foreach (HighscoreEntry highscoreEntry in highscoreEntryList)
            {
                CreateHighScoreEntryTransform(highscoreEntry, entryContainer, highscoreEntryTransformList);
            }
            Highscores highscores = new Highscores { highscoreEntryList = highscoreEntryList };
            string json = JsonUtility.ToJson(highscores);
            PlayerPrefs.SetString("highscoreTable", json);
            PlayerPrefs.Save();
            Debug.Log(PlayerPrefs.GetString("highscoreTable"));
        }
        else
        {

        }

You’re saving it as a PlayerPref. You’ll need to look up where PlayerPrefs get saved depending on what platform you are running on. But in the editor on Windows, PlayerPrefs are saved in the registry.

If you look on the asset store, there are free PlayerPref viewers that you can add to Unity so you can access the PlayerPrefs in the editor.

Note, it’s hard to tell, but since you’re doing highscores, I’m assuming you’re creating this at runtime. Which you can do in play mode in Unity.

It`s From a code monkey tutorial, was hard to follow, but got it working.
Apparently its json file he says ? he converts the playerpref to json he says or am i lost some where >

First you need to get familiar with the role that JSON plays. Your wording above makes me think you haven’t worked through the basics yet.

Serialization means turning a piece of structured data into a string. One format is JSON. PlayerPrefs can save strings. Strings can also be written to a file on disk.

Strings can then be deserialized back into a piece of structured data.

Problems with Unity “tiny lite” built-in JSON:

In general I highly suggest staying away from Unity’s JSON “tiny lite” package. It’s really not very capable at all and will silently fail on very common data structures, such as Dictionaries and Hashes and ALL properties.

Instead grab Newtonsoft JSON .NET off the asset store for free, or else install it from the Unity Package Manager (Window → Package Manager).

https://assetstore.unity.com/packages/tools/input-management/json-net-for-unity-11347

Also, always be sure to leverage sites like:

https://jsonlint.com
https://json2csharp.com
https://csharp2json.io

Json is a format. So yes, it’s formatted in a way that a json parser can read it. But as you have it saved, it’s just a string. I usually just call it a json string, because it’s a string that can be deserialized by a json parser. But, you are just saving it currently as a PlayerPref, so it’s not a json file.

I’m going to recommend that you opt to use PlayerPrefs or to write a json file (which is what I do) and then to practice outside of trying to do anything important. In your startup scene simply call some code that sets a “preference” and writes it and then reads it back. Locate the file (as was mentioned) it depends upon the platform, i.e. in Windows you will find it on your hard drive, in VR you will find it on your headset. It is all documented.

And I use the JsonUtility and haven’t encountered any issues. If it doesn’t meet your needs you can use another library but if it works why not use the built-in option?

Always great to see PlayerPrefs being abused. As the class suggests it’s for Player Preferences.
Settings like resolution, volume, bloom etc.
Whilst yes the mechanism to store a string is one of its capabilities it’s not what the class was meant for.
Personally I don’t like the abuse of PlayerPrefs. It is easy to write your own class that writes / reads json.
Writing files to disk is usually done in the Application.persistentDataPath folder

Here’s an example that uses Unity’s lightweight JsonUtility.
It can probably be improved but it is an example to start with.
It can be replaced with NewtonsoftJson which is more flexible it allows polymorphism and deserialization of collections directly. It depends on your needs.

using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;

namespace Unity.JsonSerialization
{
    public static class UnityJsonHandler
    {
        /// <summary>
        /// Writes the given object as json to the persistent data path
        /// </summary>
        /// <param name="instance">The object to be saved</param>
        /// <param name="relativePath">The relative path within the <see cref="UnityEngine.Application.persistentDataPath"/> folder</param>
        /// <param name="fileName">Name of the file without extension</param>
        /// <param name="overwrite">Whether to overwrite the file</param>
        /// <exception cref="InvalidTypeException">Exception thrown when the object type is UnityEngine.Object or a collection</exception>
        /// <exception cref="FileAlreadyExistsException">Exception thrown when the file already exists and overwrite is set to false</exception>
        public static async Task SaveToJsonFileAsync(object instance, string relativePath, string fileName, bool overwrite = true)
        {
            if (instance == null) throw new NullReferenceException($"{nameof(instance)} was null.");
      
            var type = instance.GetType();
            var filePath = CreateAbsoluteJsonPath(relativePath, fileName);

            SerializeTypeCheck(type);
            WriteDirectoryCheck(relativePath);
            OverwriteFileCheck(filePath, overwrite);

            var json = JsonUtility.ToJson(instance);
            await File.WriteAllTextAsync(filePath, json);

        #if DEBUG
            if (File.Exists(filePath))
                Debug.Log($"Written json successfully to {filePath}");
        #endif
        }

        public static async Task<T> ReadFromTextAsync<T>(string relativePath, string fileName)
        {
            var type = typeof(T);
            DeserializeTypeCheck(type);

            var filePath = CreateAbsoluteJsonPath(relativePath, fileName);
            if (!File.Exists(filePath))
                throw new FileDoesNotExistException($"Invalid path to file {filePath}");

            // Read json from disk asynchronous and deserialize
            var json = await File.ReadAllTextAsync(filePath);
            return JsonUtility.FromJson<T>(json);
        }
  
        public static async Task OverwriteFromTextAsync<T>(T instance, string relativePath, string fileName)
        {
            if (instance == null) throw new NullReferenceException($"{nameof(instance)} was null.");
      
            var type = typeof(T);
            DeserializeTypeCheck(type);

            var filePath = CreateAbsoluteJsonPath(relativePath, fileName);
            if (!File.Exists(filePath))
                throw new FileDoesNotExistException($"Invalid path to file {filePath}");

            // Read json from disk asynchronous and deserialize
            var json = await File.ReadAllTextAsync(filePath);
            JsonUtility.FromJsonOverwrite(json, instance);
        }

        public static void DeleteJsonFile(string relativePath, string fileName)
        {
            var filePath = CreateAbsoluteJsonPath(relativePath, fileName);
            if (!File.Exists(filePath))
            {
                Debug.LogWarning($"Cannot remove file {fileName}.json. It does not exist at {CreateAbsoluteFolderPath(relativePath)}");
                return;
            }

            // Delete file
            File.Delete(filePath);

            // Delete folder if the folder is now empty
            var folderPath = CreateAbsoluteFolderPath(relativePath);
            if (!Directory.EnumerateFileSystemEntries(folderPath).Any())
                Directory.Delete(folderPath);

        #if DEBUG
            Debug.Log($"Removed file {fileName}.json at {folderPath}");
        #endif
        }

        private static string CreateAbsoluteFolderPath(string relativePath) => $"{Application.persistentDataPath}/{relativePath}";
        private static string CreateAbsoluteJsonPath(string relativePath, string fileName) => $"{Application.persistentDataPath}/{relativePath}/{fileName}.json";

        private static void DeserializeTypeCheck(Type type)
        {
            // Prevents the use of UnityEngine.Object type deserialization.
            // UnityEngine.Object has its own ways of creating objects.
            if (type.IsAssignableFrom(typeof(UnityEngine.Object)))
                throw new InvalidTypeException("Cannot deserialize json into UnityEngine.Object type");

            // JsonUtility cannot handle collections directly they must be wrapped
            if (typeof(ICollection).IsAssignableFrom(type) || typeof(IEnumerable).IsAssignableFrom(type))
                throw new InvalidTypeException("Cannot deserialize json directly in a ICollection or IEnumerable type");

            if (!type.IsSerializable)
                throw new NonSerializableTypeException($"The object provided of type {type} is not serializable");
        }

        private static void SerializeTypeCheck(Type type)
        {
            if (type.IsAssignableFrom(typeof(UnityEngine.Object)))
                throw new InvalidTypeException("Cannot serialize to json using UnityEngine.Object inherited types");

            if (typeof(ICollection).IsAssignableFrom(type) || typeof(IEnumerable).IsAssignableFrom(type))
                throw new InvalidTypeException("Cannot serialize to json a collection type directly");

            if (!type.IsSerializable)
                throw new NonSerializableTypeException($"The object provided of type {type} is not serializable");
        }

        private static void WriteDirectoryCheck(string relativePath)
        {
            var folderPath = CreateAbsoluteFolderPath(relativePath);
            if (!Directory.Exists(folderPath))
                Directory.CreateDirectory(folderPath);
        }

        private static void OverwriteFileCheck(string absoluteFilePath, bool overwrite = true)
        {
            if (!overwrite && File.Exists(absoluteFilePath))
                throw new FileAlreadyExistsException($"File already exists at {absoluteFilePath}");
        }
    }
}

public class InvalidTypeException : Exception
{
    public InvalidTypeException(string message) : base(message)
    {
    }
}

public class NonSerializableTypeException : Exception
{
    public NonSerializableTypeException(string message) : base(message)
    {
    }
}

public class FileDoesNotExistException : Exception
{
    public FileDoesNotExistException(string message) : base(message)
    {
    }
}

public class FileAlreadyExistsException : Exception
{
    public FileAlreadyExistsException(string message) : base(message)
    {
    }
}

A simple component example:

using System;
using UnityEngine;
using Unity.JsonSerialization;

namespace Testing
{
    public class TestExample : MonoBehaviour
    {
        private async void Awake()
        {
            var data = new DataThing { Name = "John Doe", Number = 100 };
            await UnityJsonHandler.SaveToJsonFileAsync(data, "JSON", "jsonFile");
      
            var fromJson = await UnityJsonHandler.ReadFromTextAsync<DataThing>("JSON", "jsonFile");
            Debug.Log(fromJson, this);
      
            //UnityJsonHandler.DeleteJsonFile("JSON", "jsonFile");
        }
    }

    [Serializable]
    public class DataThing
    {
        public int Number;
        public string Name;

        public override string ToString() => $"{Name} - {Number}";
    }
}

@Kurt-Dekker im just quoting what code monkey said, yup im trying to learn how to properly save and load.
@MaskedMouse yeah it does get abused lol, i will try what you have provided thank you.

Apparently I had a little mistake in there (I fixed it). DeserializeTypeCheck and SerializeTypeCheck were the other way around. But yeah feel free to iterate on it however you feel like.
Or take it in as inspiration to make your own however you see fit.
I used an async version because of the file handling. But it can be written as a synchronized version as well.

Yeah i got no idea why in the tutorial he is writing to player prefs, i have changed it now.
Any input is great as it will help me learn my errors, thank you for all your replies.
I load it in a different function, and have a class that keeps my name and score.

        if (!File.Exists(Application.dataPath + "/save.txt"))
        {
            {
                highscoreEntryList = new List<HighscoreEntry>()
        {
            new HighscoreEntry{score = 0, name = "???"},
        };


                highscoreEntryTransformList = new List<Transform>();
                foreach (HighscoreEntry highscoreEntry in highscoreEntryList)
                {
                    CreateHighScoreEntryTransform(highscoreEntry, entryContainer, highscoreEntryTransformList);
                }
                Highscores highscores = new Highscores { highscoreEntryList = highscoreEntryList };
                string saveString = JsonUtility.ToJson(highscores);
                File.WriteAllText(Application.dataPath + "/save.txt", saveString);
            }
        }

It needs more work :sunglasses: I don’t believe that is the correct path and in any case it is probably unique to your current situation. It needs to work on other platforms. And notice how you concatenate the path with the string twice? These must be identical yet entering it twice means you run the risk of them being different. Define it (somehow) and use the string variable instead.

The following may work for you, I made a couple of edits here so you should review it.

    private static String GetFilePath(String folder, String file)
    {
        String result;

#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
        // mac
        result = Path.Combine(Application.streamingAssetsPath, folder);
        result = Path.Combine(result, $"/{file}");

#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        // windows
        result = Path.Combine(Application.persistentDataPath, folder);
        result = Path.Combine(result, $"/{file}");

#elif UNITY_ANDROID
        // android
        result = Path.Combine(Application.persistentDataPath, folder);
        result = Path.Combine(result, $"/{file}");

#elif UNITY_IOS
        // ios
        result = Path.Combine(Application.persistentDataPath, folder);
        result = Path.Combine(result, $"/{file}");
#endif

        return result;
    }

Oh I meant to mention I don’t even type in the folder and file name. Those come from a Config class that contains static unchanging data so Config.DataFolder and Config.DataFile would be what I would pass to it.

public static String DataFolder = "data";
public static String DataFile = "player.data";

Why are you defining the same thing 3x (for Windows, Android and iOS)?
For MacOS I’d use persistentDataPath as well.

Personally I don’t use Path.Combine at all. It even caused me issues at some point creating invalid paths. Can’t remember when / where. While Path.Combine was supposed to create a valid path.
I haven’t run into any issues just using the forward slashes in the path.

Just doing once var absolutePath = $"{Application.persistentDataPath}/{folder}/{file}"; should be fine for an absolute path.

1 Like

@tleylan
That is in a “awake” with a if ( ! ) to see if if the text file is there, if not it creates one.
Do you mean i created the String twice ? in this situation if i take it out it don`t work.

                string saveString = JsonUtility.ToJson(highscores);
                File.WriteAllText(Application.dataPath + "/save.txt", saveString);

^^ can i refactor the save string somewhere else maybe ?

I making it for pc and i have considered mobile, but i will cross the road in the future and thank you for
the reference.

To me it seems everybody has there own methods, its great to see peoples views as it helps me
improve and implement better code.
I can share the hole code if you have time to help, it might be good too see somebody elses refactoring of it. Thank you for taking time to help.

Oh excellent. I found this code in an article or message reply somewhere. The duplicates are compile-time constructs so only one would be in the build but you are 100% correct when I finally looked it up IOS uses persistentDataPath as well. As it should BTW to turn what are different paths into a standard one for coding.

The Path.Combine was similarly in that example and the person was checking for an empty file name (which I removed) but short story is I’m replacing the method immediately as a result of your suggestions.

That’s the string but you can’t just take it out. The idea is not to reference string constants that need to be identical two ore more times. Something like the following should work. You define this in your class and then _filePath can be used as many places as you choose with zero chance that one of them is spelled differently. Especially if you decide to one day change the file name to save.dat or player.txt or anything else. You have one place to change the definition.

private _filePath = Application.dataPath + “/save.txt”;

In my case it is part of library class that handles as many different files as I care to use so I place the logic into a method which with @Penitent1 insight has been greatly simplified…

private static String GetFilePath(String folder, String file)
{
   return $"{Application.persistentDataPath}/{folder}/{file}";
}