Can you read a json class with a custom string

I want to instantiate the objects by name after reading a json file.

	public ClassesChange myObject;
	public TextAsset json;
	private GameObject model;
	public GameObject[] objectList;
	
	void Start()
	{
		JsonUtility.FromJsonOverwrite(json.text, myObject);
		
		foreach (GameObject anObject in objectList)
		{
			Generate(anObject.name);
		}
	}
	
	void Generate(string anObject)
	{
		Debug.Log(anObject);
		
		foreach (MyClassChange anObject in myObject.anObject)
		{
			model = Instantiate(objectList[0], new Vector3(
			float.Parse(anObject.xPos), 
			float.Parse(anObject.yPos), 
			float.Parse(anObject.zPos)), Quaternion.Euler(
			float.Parse(anObject.xRot), 
			float.Parse(anObject.yRot), 
			float.Parse(anObject.zRot)));
		}
	}

error CS1061: 'ClassesChange' does not contain a definition for 'anObject' and no accessible extension method 'anObject' accepting a first argument of type 'ClassesChange' could be found (are you missing a using directive or an assembly reference?)

Can you post ClassesChange?

I mean the error is just saying ClassesChange has no public member called anObject.

[System.Serializable]
public class ClassesChange
{
    //objects is case sensitive and must match the string "objects" in the JSON.
	
	//prefabs
	public MyClassChange[] anObject1;
	public MyClassChange[] anObject2;
}

json.json

{
	"anObject1":
	[
		{
			"xPos": "0.000000", 
			"yPos": "0.000000", 
			"zPos": "0.000000", 
			"xRot": "0.000000", 
			"yRot": "0.000000", 
			"zRot": "0.000000"
		}
	],
	"anObject2":
	[
		{
			"xPos": "0.000000", 
			"yPos": "0.000000", 
			"zPos": "0.000000", 
			"xRot": "0.000000", 
			"yRot": "0.000000", 
			"zRot": "0.000000"
		}
	]
}
[System.Serializable]
public class MyClassChange
{
	//these variables are case sensitive and must match the strings "name" and "xPos" in the JSON.
	public string xPos;
	public string yPos;
	public string zPos;
	public string xRot;
	public string yRot;
	public string zRot;
}

Right so you have members anObject1 and anObject2, so naturally you cant enumerate the nonexistent member anObject. You would have to enumerate each of those members individually.

Though your naming and json seems to assume there is only one element per array? Do you mean for these to be collections?

Though it would make sense for your json to instead have a collection of your serializable MyClassChange object, and simply enumerate that instead:

You would have to adjust the class and the json to have a collection of your MyClassChange.

[System.Serializable]
public class ClassesChange
{
    public MyClassesChange[] ChangeClasses;
}

And then fix up the json to match.

In any case, there’s also no reason for MyClassChange to be filled with strings. If the json has float compatible values, your C# should have float values as well. No reason for fragile parsing of strings into floats.

I thought this might work but I was not eager to try because I thought I was writing the json file correctly

I mean, are you writing this JSON to disk via code, or are you manually writing out this JSON in a text editor? If the latter, I’m not sure why you would. That sounds pretty inefficient.

To disk via code it’s just formatted incorrectly

What do you mean by ‘formatted incorrectly’? It writes to disk in whatever structure you give it. And Unity’s JsonUtility isn’t that bad that it writes incorrectly formatted JSON.

It’s formatted in Visual Studio

Sorry that’s just even more confusing. Why are you formatting it in Visual Studio? You said you’re writing it to disk via code, but then formatting it in Visual Studio? That doesn’t make sense.

If you’re writing it to disk via code, you should not be manually changing the structure of the JSON (unless to fix it to match changes in your data structure, but that’s beside my point). Just write it to disk, then read it from disk.

Basically… I am writing a JSON from a visual studio program and then reading it in a Unity game

Then that’s not ‘writing to disk via code’. That’s manually writing JSON, which you really shouldn’t do as it will be glacial.

Lets get more to the point, you said you wanted:

Yes you can do that, but you actually… need to write the name to disk to make that possible.

I think this will just be easier if I just make an example:
PersistenceExample.unitypackage (20.7 KB)

The code for anyone who doesn’t want to download the package:

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

public sealed class PersistanceExample : MonoBehaviour
{
	#region Inspector Fields

	[SerializeField, Min(0.1f)]
	private float _spawnRadius = 1.0f;

	[SerializeField]
	private List<GameObject> _prefabs = new();

	#endregion

	#region Internal Members

	private readonly List<GameObject> _prefabInstances = new();

	#endregion

	#region Properties

	public static string SavePath => Application.persistentDataPath + "/PrefabInstanceData.txt";

	#endregion
	
	#region Unity Callbacks
	
	private void Awake()
	{
		TryLoadExistingInstanceData();
	}
	
	private void Update()
	{
		if (Input.GetKeyDown(KeyCode.R) == true)
		{
			ClearAllActivePrefabs();
		}

		if (Input.GetKeyDown(KeyCode.Space) == true)
		{
			SpawnRandomPrefab();
		}

		if (Input.GetKeyDown(KeyCode.S) == true)
		{
			SaveActiveInstances();
		}
	}

	private void OnDrawGizmosSelected()
	{
#if UNITY_EDITOR
		UnityEditor.Handles.color = Color.blue;
		UnityEditor.Handles.DrawWireDisc(transform.position, transform.up, _spawnRadius);
#endif
	}

	#endregion

	#region Internal Methods

	private void TryLoadExistingInstanceData()
	{
		string path = SavePath;
		bool exists = File.Exists(path);
		if (exists == false)
		{
			return;
		}

		string json = File.ReadAllText(path);
		var instanceData = JsonUtility.FromJson<PrefabInstanceData>(json);
		foreach (PrefabInstance prefabInstance in instanceData.PrefabInstances)
		{
			string name = prefabInstance.Name;
			if (TryGetPrefabByName(name, out var prefab) == true)
			{
				Vector3 position = prefabInstance.Position;
				Quaternion rotation = prefabInstance.Rotation;
				SpawnPrefab(prefab, position, rotation);
			}
		}
	}

	private void SpawnRandomPrefab()
	{
		int index = Random.Range(0, _prefabs.Count);
		var prefab = _prefabs[index];
		Vector2 spawnPoint2D = Random.insideUnitCircle * _spawnRadius;
		Vector3 position = new (spawnPoint2D.x, 0, spawnPoint2D.y);
		SpawnPrefab(prefab, position, Quaternion.identity);
	}

	private void SpawnPrefab(GameObject prefab, Vector3 position, Quaternion rotation)
	{
		var prefabInstance = Instantiate(prefab, position, rotation);
		_prefabInstances.Add(prefabInstance);
	}

	private void ClearAllActivePrefabs()
	{
		foreach (var prefabInstance in _prefabInstances)
		{
			Destroy(prefabInstance);
		}

		_prefabInstances.Clear();
	}

	private bool TryGetPrefabByName(string name, out GameObject prefab)
	{
		prefab = null;
		foreach (GameObject p in _prefabs)
		{
			bool nameMatch = name.StartsWith(p.name); // instances have names ending with (Clone)
			if (nameMatch == true)
			{
				prefab = p;
				return true;
			}
		}

		return false;
	}

	private void SaveActiveInstances()
	{
		var instanceData = new PrefabInstanceData();

		foreach (GameObject prefabInstance in _prefabInstances)
		{
			instanceData.AddPrefabInstance(prefabInstance);
		}

		string savePath = SavePath;
		string json = JsonUtility.ToJson(instanceData, prettyPrint: true);
		File.WriteAllText(savePath, json);
	}

	#endregion

	#region Nested Types

	[System.Serializable]
	public sealed class PrefabInstanceData
	{
		#region Internal Members

		[SerializeField]
		private List<PrefabInstance> _prefabInstances = new();

		#endregion

		#region Properties

		public IReadOnlyList<PrefabInstance> PrefabInstances => _prefabInstances;

		#endregion

		#region Methods

		public void AddPrefabInstance(GameObject prefabInstance)
		{
			var instance = new PrefabInstance(prefabInstance);
			_prefabInstances.Add(instance);
		}

		#endregion
	}

	[System.Serializable]
	public struct PrefabInstance
	{
		#region Internal Members

		[SerializeField]
		private string _name;

		[SerializeField]
		private Vector3 _position;

		[SerializeField]
		private Quaternion _rotation;

		#endregion

		#region Properties

		public readonly string Name => _name;

		public readonly Vector3 Position => _position;

		public readonly Quaternion Rotation => _rotation;

		#endregion

		#region Constructors

		public PrefabInstance(GameObject prefab)
		{
			_name = prefab.name;
			_position = prefab.transform.position;
			_rotation = prefab.transform.rotation;
		}

		#endregion
	}

	#endregion
}

Spawn a prefab with Space, reset them with R, and save the current ones with S. It will attempt to spawn prefabs from existing data in Awake.

Example of the JSON:

{
    "_prefabInstances": [
        {
            "_name": "Sphere(Clone)",
            "_position": {
                "x": 2.902937173843384,
                "y": 0.0,
                "z": -3.939030170440674
            },
            "_rotation": {
                "x": 0.0,
                "y": 0.0,
                "z": 0.0,
                "w": 1.0
            }
        },
        {
            "_name": "Sphere(Clone)",
            "_position": {
                "x": 4.597560882568359,
                "y": 0.0,
                "z": -0.8488390445709229
            },
            "_rotation": {
                "x": 0.0,
                "y": 0.0,
                "z": 0.0,
                "w": 1.0
            }
        },
        {
            "_name": "Sphere(Clone)",
            "_position": {
                "x": 1.465227723121643,
                "y": 0.0,
                "z": -0.9499194622039795
            },
            "_rotation": {
                "x": 0.0,
                "y": 0.0,
                "z": 0.0,
                "w": 1.0
            }
        },
        {
            "_name": "Capsule(Clone)",
            "_position": {
                "x": -2.340095043182373,
                "y": 0.0,
                "z": -3.0521860122680666
            },
            "_rotation": {
                "x": 0.0,
                "y": 0.0,
                "z": 0.0,
                "w": 1.0
            }
        },
        {
            "_name": "Cube(Clone)",
            "_position": {
                "x": -4.0832319259643559,
                "y": 0.0,
                "z": -0.263769268989563
            },
            "_rotation": {
                "x": 0.0,
                "y": 0.0,
                "z": 0.0,
                "w": 1.0
            }
        }
    ]
}
1 Like

This is my working code

	public ClassesChange myObject;
	public TextAsset json;
	private GameObject model;
	
	void Awake()
	{
		JsonUtility.FromJsonOverwrite(json.text, myObject);
	}
	
	void Start()
	{
		Generate();
	}
	
	void Generate()
	{
		foreach (MyClassChange objects in myObject.objects)
		{
			if (Resources.Load(objects.name))
			{
				model = Instantiate(Resources.Load(objects.name) as GameObject, new Vector3(
				float.Parse(objects.xPos), 
				float.Parse(objects.yPos), 
				float.Parse(objects.zPos)), Quaternion.Euler(
				float.Parse(objects.xRot), 
				float.Parse(objects.yRot), 
				float.Parse(objects.zRot)));
			}
		}
	}

Well, he said

which is a weird way to phrase it but he essentially wrote a separate program in VS (could be a C++ or C# program or something completely different) that created the json file.

It doesn’t really matter where the data comes from. Though if you have choosen this layout, you could have choosen a better structure. Especially storing values as strings means they need to be parsed manually into floats. Here you have to be careful with local culture settings because half the world is using a comma as decimal point while the other half uses a dot. So in order to parse numbers from string you usually want to use the InvariantCulture. That’s why parsing it manually is not a good idea.

I’ve written a compact json parser “SimpleJSON” which does not map the values to custom C# objects but provides the json data in dedicated classes to represent a json array, json object, json string, etc. I made a Unity specific extension file (that just needs to sit next to the SimpleJSON.cs file) that adds some automatic conversion for vectors, colors, etc. Though it also adds some ReadVector3 methods which even allow you to specify custom component names. So when you have a json object, you can simply do

Vector3 pos = someJsonObj.ReadVector3("xPos", "yPos", "zPos");
Vector3 rot = someJsonObj.ReadVector3("xRot", "yRot", "zRot");

Though it usually makes more sense to separate individual vectors. My framework can automatically parse strings or actual number values. The normal ReadVector3 will expect either x, y and z components or that it’s an array in those cases the object can be directly converted.

So assuming json like this:

{
	"anObject1":
	[
		{
			"pos": [0.0, 0.0, 0.0], 
			"rot": [0.0, 0.0, 0.0]
		}
	],
	"anObject2":
	[
		{
			"pos": {"x":0.0, "y":0.0, "z":0.0},
			"rot": {"x":0.0, "y":0.0, "z":0.0}
		}
	]
}

The values could be parse like this

JSONNode root = JSON.Parse(yourJsonText);

Vector3 pos = root["anObject1"][0]["pos"];
Vector3 rot = root["anObject1"][0]["rot"];
// [ ... ]

or iterate over the array

foreach (JSONNode obj in root["anObject1"])
{
    Vector3 pos = obj["pos"];
    Vector3 rot = obj["rot"];
    // [ ... ]
}

Though it’s up to you how you want to structure your data. Depending on the used framework there may be certain limitations.

1 Like

Yeah admittedly I read that as if they were using visual studio to write the JSON.

But even then, it would make more sense to write this data from Unity itself. Would be a lot simpler to write an editor window that lets you enter the data to be written to json. This would give you access to Unity specific data types as well.

I guess the question is why does this need to be done via JSON? Is it for save game stuff? Level editor stuff?

1 Like

Well, it may not be a game at all :slight_smile: Though I agree that whereever the data comes from, when it’s processed / generated in a standalone C# 7 C++ application it could most likely be done directly in Unity as well. Though it could be a real-time recording of something, but who knows. When directly processing data there would be no need to go through json.

SimpleJSON is being used now and I can read keys and how many there are

Code:

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

namespace SimpleJSON
{
public class instance : MonoBehaviour
{
	public TextAsset json;
	public JSONNode jsonNode;
	public List<string> keys;
	public List<int> keysAmount;
	
	void Awake()
	{
		jsonNode = JSON.Parse(json.text);
	}
	
	void Start()
	{
		Generate();
	}
	
	void Generate()
	{
		jsonNode = JSON.Parse(json.text);

		foreach (KeyValuePair<string, JSONNode> aKey in (JSONObject)jsonNode["raw_objects"])
		{
			keys.Add(aKey.Key);
			keysAmount.Add(jsonNode["raw_objects"][aKey.Key].Count);
		}

		for (int i = 0; i <	keys.Count - 1; i++ ) 
		{
			for (int z = 0; z <	keysAmount[i]; z++ ) 
			{
				Debug.Log(keys[i] + " Amount: " + keysAmount[i]);
			}
		}
	}
}
}