Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question json file location

Discussion in 'Scripting' started by Mattyerial, Jan 4, 2023.

  1. Mattyerial

    Mattyerial

    Joined:
    Oct 5, 2018
    Posts:
    51
    Hello i`m 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 ? i`m a bit confused.


    Code (CSharp):
    1.         if(PlayerPrefs.GetString("highscoreTable") == null)
    2.         {
    3.             highscoreEntryList = new List<HighscoreEntry>()
    4.         {
    5.             new HighscoreEntry{score = 0, name = "???"},
    6.         };
    7.  
    8.  
    9.             highscoreEntryTransformList = new List<Transform>();
    10.             foreach (HighscoreEntry highscoreEntry in highscoreEntryList)
    11.             {
    12.                 CreateHighScoreEntryTransform(highscoreEntry, entryContainer, highscoreEntryTransformList);
    13.             }
    14.             Highscores highscores = new Highscores { highscoreEntryList = highscoreEntryList };
    15.             string json = JsonUtility.ToJson(highscores);
    16.             PlayerPrefs.SetString("highscoreTable", json);
    17.             PlayerPrefs.Save();
    18.             Debug.Log(PlayerPrefs.GetString("highscoreTable"));
    19.         }
    20.         else
    21.         {
    22.  
    23.         }
     
  2. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,150
    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.

    https://assetstore.unity.com/packages/tools/utilities/playerprefs-editor-167903

    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.
     
  3. Mattyerial

    Mattyerial

    Joined:
    Oct 5, 2018
    Posts:
    51
    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 >
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,951
    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
     
  5. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,150
    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.
     
  6. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    531
    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?
     
  7. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,064
    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.
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.IO;
    4. using System.Linq;
    5. using System.Threading.Tasks;
    6. using UnityEngine;
    7.  
    8. namespace Unity.JsonSerialization
    9. {
    10.     public static class UnityJsonHandler
    11.     {
    12.         /// <summary>
    13.         /// Writes the given object as json to the persistent data path
    14.         /// </summary>
    15.         /// <param name="instance">The object to be saved</param>
    16.         /// <param name="relativePath">The relative path within the <see cref="UnityEngine.Application.persistentDataPath"/> folder</param>
    17.         /// <param name="fileName">Name of the file without extension</param>
    18.         /// <param name="overwrite">Whether to overwrite the file</param>
    19.         /// <exception cref="InvalidTypeException">Exception thrown when the object type is UnityEngine.Object or a collection</exception>
    20.         /// <exception cref="FileAlreadyExistsException">Exception thrown when the file already exists and overwrite is set to false</exception>
    21.         public static async Task SaveToJsonFileAsync(object instance, string relativePath, string fileName, bool overwrite = true)
    22.         {
    23.             if (instance == null) throw new NullReferenceException($"{nameof(instance)} was null.");
    24.      
    25.             var type = instance.GetType();
    26.             var filePath = CreateAbsoluteJsonPath(relativePath, fileName);
    27.  
    28.             SerializeTypeCheck(type);
    29.             WriteDirectoryCheck(relativePath);
    30.             OverwriteFileCheck(filePath, overwrite);
    31.  
    32.             var json = JsonUtility.ToJson(instance);
    33.             await File.WriteAllTextAsync(filePath, json);
    34.  
    35.         #if DEBUG
    36.             if (File.Exists(filePath))
    37.                 Debug.Log($"Written json successfully to {filePath}");
    38.         #endif
    39.         }
    40.  
    41.         public static async Task<T> ReadFromTextAsync<T>(string relativePath, string fileName)
    42.         {
    43.             var type = typeof(T);
    44.             DeserializeTypeCheck(type);
    45.  
    46.             var filePath = CreateAbsoluteJsonPath(relativePath, fileName);
    47.             if (!File.Exists(filePath))
    48.                 throw new FileDoesNotExistException($"Invalid path to file {filePath}");
    49.  
    50.             // Read json from disk asynchronous and deserialize
    51.             var json = await File.ReadAllTextAsync(filePath);
    52.             return JsonUtility.FromJson<T>(json);
    53.         }
    54.  
    55.         public static async Task OverwriteFromTextAsync<T>(T instance, string relativePath, string fileName)
    56.         {
    57.             if (instance == null) throw new NullReferenceException($"{nameof(instance)} was null.");
    58.      
    59.             var type = typeof(T);
    60.             DeserializeTypeCheck(type);
    61.  
    62.             var filePath = CreateAbsoluteJsonPath(relativePath, fileName);
    63.             if (!File.Exists(filePath))
    64.                 throw new FileDoesNotExistException($"Invalid path to file {filePath}");
    65.  
    66.             // Read json from disk asynchronous and deserialize
    67.             var json = await File.ReadAllTextAsync(filePath);
    68.             JsonUtility.FromJsonOverwrite(json, instance);
    69.         }
    70.  
    71.         public static void DeleteJsonFile(string relativePath, string fileName)
    72.         {
    73.             var filePath = CreateAbsoluteJsonPath(relativePath, fileName);
    74.             if (!File.Exists(filePath))
    75.             {
    76.                 Debug.LogWarning($"Cannot remove file {fileName}.json. It does not exist at {CreateAbsoluteFolderPath(relativePath)}");
    77.                 return;
    78.             }
    79.  
    80.             // Delete file
    81.             File.Delete(filePath);
    82.  
    83.             // Delete folder if the folder is now empty
    84.             var folderPath = CreateAbsoluteFolderPath(relativePath);
    85.             if (!Directory.EnumerateFileSystemEntries(folderPath).Any())
    86.                 Directory.Delete(folderPath);
    87.  
    88.         #if DEBUG
    89.             Debug.Log($"Removed file {fileName}.json at {folderPath}");
    90.         #endif
    91.         }
    92.  
    93.         private static string CreateAbsoluteFolderPath(string relativePath) => $"{Application.persistentDataPath}/{relativePath}";
    94.         private static string CreateAbsoluteJsonPath(string relativePath, string fileName) => $"{Application.persistentDataPath}/{relativePath}/{fileName}.json";
    95.  
    96.         private static void DeserializeTypeCheck(Type type)
    97.         {
    98.             // Prevents the use of UnityEngine.Object type deserialization.
    99.             // UnityEngine.Object has its own ways of creating objects.
    100.             if (type.IsAssignableFrom(typeof(UnityEngine.Object)))
    101.                 throw new InvalidTypeException("Cannot deserialize json into UnityEngine.Object type");
    102.  
    103.             // JsonUtility cannot handle collections directly they must be wrapped
    104.             if (typeof(ICollection).IsAssignableFrom(type) || typeof(IEnumerable).IsAssignableFrom(type))
    105.                 throw new InvalidTypeException("Cannot deserialize json directly in a ICollection or IEnumerable type");
    106.  
    107.             if (!type.IsSerializable)
    108.                 throw new NonSerializableTypeException($"The object provided of type {type} is not serializable");
    109.         }
    110.  
    111.         private static void SerializeTypeCheck(Type type)
    112.         {
    113.             if (type.IsAssignableFrom(typeof(UnityEngine.Object)))
    114.                 throw new InvalidTypeException("Cannot serialize to json using UnityEngine.Object inherited types");
    115.  
    116.             if (typeof(ICollection).IsAssignableFrom(type) || typeof(IEnumerable).IsAssignableFrom(type))
    117.                 throw new InvalidTypeException("Cannot serialize to json a collection type directly");
    118.  
    119.             if (!type.IsSerializable)
    120.                 throw new NonSerializableTypeException($"The object provided of type {type} is not serializable");
    121.         }
    122.  
    123.         private static void WriteDirectoryCheck(string relativePath)
    124.         {
    125.             var folderPath = CreateAbsoluteFolderPath(relativePath);
    126.             if (!Directory.Exists(folderPath))
    127.                 Directory.CreateDirectory(folderPath);
    128.         }
    129.  
    130.         private static void OverwriteFileCheck(string absoluteFilePath, bool overwrite = true)
    131.         {
    132.             if (!overwrite && File.Exists(absoluteFilePath))
    133.                 throw new FileAlreadyExistsException($"File already exists at {absoluteFilePath}");
    134.         }
    135.     }
    136. }
    137.  
    138. public class InvalidTypeException : Exception
    139. {
    140.     public InvalidTypeException(string message) : base(message)
    141.     {
    142.     }
    143. }
    144.  
    145. public class NonSerializableTypeException : Exception
    146. {
    147.     public NonSerializableTypeException(string message) : base(message)
    148.     {
    149.     }
    150. }
    151.  
    152. public class FileDoesNotExistException : Exception
    153. {
    154.     public FileDoesNotExistException(string message) : base(message)
    155.     {
    156.     }
    157. }
    158.  
    159. public class FileAlreadyExistsException : Exception
    160. {
    161.     public FileAlreadyExistsException(string message) : base(message)
    162.     {
    163.     }
    164. }

    A simple component example:
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using Unity.JsonSerialization;
    4.  
    5. namespace Testing
    6. {
    7.     public class TestExample : MonoBehaviour
    8.     {
    9.         private async void Awake()
    10.         {
    11.             var data = new DataThing { Name = "John Doe", Number = 100 };
    12.             await UnityJsonHandler.SaveToJsonFileAsync(data, "JSON", "jsonFile");
    13.      
    14.             var fromJson = await UnityJsonHandler.ReadFromTextAsync<DataThing>("JSON", "jsonFile");
    15.             Debug.Log(fromJson, this);
    16.      
    17.             //UnityJsonHandler.DeleteJsonFile("JSON", "jsonFile");
    18.         }
    19.     }
    20.  
    21.     [Serializable]
    22.     public class DataThing
    23.     {
    24.         public int Number;
    25.         public string Name;
    26.  
    27.         public override string ToString() => $"{Name} - {Number}";
    28.     }
    29. }
     
    Last edited: Jan 5, 2023
  8. Mattyerial

    Mattyerial

    Joined:
    Oct 5, 2018
    Posts:
    51
    @Kurt-Dekker i`m just quoting what code monkey said, yup i`m 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.
     
  9. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,064
    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.
     
  10. Mattyerial

    Mattyerial

    Joined:
    Oct 5, 2018
    Posts:
    51
    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.

    Code (CSharp):
    1.         if (!File.Exists(Application.dataPath + "/save.txt"))
    2.         {
    3.             {
    4.                 highscoreEntryList = new List<HighscoreEntry>()
    5.         {
    6.             new HighscoreEntry{score = 0, name = "???"},
    7.         };
    8.  
    9.  
    10.                 highscoreEntryTransformList = new List<Transform>();
    11.                 foreach (HighscoreEntry highscoreEntry in highscoreEntryList)
    12.                 {
    13.                     CreateHighScoreEntryTransform(highscoreEntry, entryContainer, highscoreEntryTransformList);
    14.                 }
    15.                 Highscores highscores = new Highscores { highscoreEntryList = highscoreEntryList };
    16.                 string saveString = JsonUtility.ToJson(highscores);
    17.                 File.WriteAllText(Application.dataPath + "/save.txt", saveString);
    18.             }
    19.         }
     
  11. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    531
    It needs more work :cool: 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.

    Code (CSharp):
    1.  
    2.     private static String GetFilePath(String folder, String file)
    3.     {
    4.         String result;
    5.  
    6. #if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
    7.         // mac
    8.         result = Path.Combine(Application.streamingAssetsPath, folder);
    9.         result = Path.Combine(result, $"/{file}");
    10.  
    11. #elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
    12.         // windows
    13.         result = Path.Combine(Application.persistentDataPath, folder);
    14.         result = Path.Combine(result, $"/{file}");
    15.  
    16. #elif UNITY_ANDROID
    17.         // android
    18.         result = Path.Combine(Application.persistentDataPath, folder);
    19.         result = Path.Combine(result, $"/{file}");
    20.  
    21. #elif UNITY_IOS
    22.         // ios
    23.         result = Path.Combine(Application.persistentDataPath, folder);
    24.         result = Path.Combine(result, $"/{file}");
    25. #endif
    26.  
    27.         return result;
    28.     }
    29.  
     
  12. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    531
    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.

    Code (CSharp):
    1.  
    2. public static String DataFolder = "data";
    3. public static String DataFile = "player.data";
    4.  
     
  13. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,064
    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.
     
    Kurt-Dekker likes this.
  14. Mattyerial

    Mattyerial

    Joined:
    Oct 5, 2018
    Posts:
    51
    @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.

    Code (CSharp):
    1.                 string saveString = JsonUtility.ToJson(highscores);
    2.                 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.
     
    Last edited: Jan 8, 2023
  15. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    531
    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.
     
  16. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    531
    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 @Mattyerial insight has been greatly simplified..

    Code (CSharp):
    1.  
    2. private static String GetFilePath(String folder, String file)
    3. {
    4.    return $"{Application.persistentDataPath}/{folder}/{file}";
    5. }
    6.