Deserializing JSON array into C# object - c#

I really need some help with desereliazing a JSON.
Here is my JSON : https://min-api.cryptocompare.com/data/histoday?fsym=BTC&tsym=USD
Here is the code I have so far :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Newtonsoft.Json;
public class StockManager : MonoBehaviour {
private string webString;
private CurrencyContainer container;
[SerializeField]private int currenciesToLoad;
void Start()
{
StartCoroutine(GetText());
}
void Update()
{
if (container != null)
{
Debug.Log (container.Arr);
}
else
{
Debug.Log ("null");
}
}
IEnumerator GetText()
{
using (WWW www = new WWW("https://min-api.cryptocompare.com/data/histoday?fsym=BTC&tsym=USD"))
{
yield return www;
if (www.error != null)
{
Debug.Log("Error is : " + www.error);
}
else
{
webString = "{ \"Arr\":" + www.text + "}";
container = JsonConvert.DeserializeObject<CurrencyContainer> (webString);
}
}
}
[System.Serializable]
public class Datum
{
public int time;
public double close;
public double high;
public double low;
public double open;
public double volumefrom;
public double volumeto;
}
[System.Serializable]
public class ConversionType
{
public string type;
public string conversionSymbol;
}
[System.Serializable]
public class Example
{
public string Response;
public int Type;
public bool Aggregated;
public IList<Datum> Data;
public int TimeTo;
public int TimeFrom;
public bool FirstValueInArray;
public ConversionType ConversionType;
}
[System.Serializable]
public class CurrencyContainer
{
public Example[] Arr;
}
}
The error I get is : JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'StockManager+Example[]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
I have no idea how to fix and any help is really appreciated.
Thanks a lot.

You have "one level to much" in your object structure, as the given JSON is only on "item" of your type Example. Try the following:
var item = JsonConvert.DeserializeObject<Example>(www.text);
See it HERE in action.

Related

Serializing / De-serializing inherited class on a procedural generated loot system (Unity C#)

I have a class called Item that has general info about the item (Name,ID, description)
I inherit from this class to create procedurally Armor / Weapons in my game.
Eg:
[System.Serializable]
public class Item
{
public string itemName;
public string itemID;
public string itemDescription;
}
[System.Serializable]
public class Armor : Item
{
public int defense;
public int armorValue;
}
In the game, when you kill an enemy, armor class procedurally generates stats and values.
My inventory system has a list of slots, that use Item class as a reference to what item is in the slot.
When I save with Json the slots, and then load again, inheritance does not work, so I get an Item class, with all the base class variables that the Armor class that was "saved" had. I have tried:
JsonConver.Serialize, JsonConvert.Deserialize explicitely on each slot (didnt work)
Casting Item as Armor if item is armor, that didnt work
Using the [JsonConstructor] attribute on the Armor constructor
I am new to Json, Serialization / De-Serialization and I try to read and understand as much as I can so any ideas / leads would be great. Thanks!
EDIT: I solved it by using Dictionaries<string,Armor> storing specifically any armors picked up using Item.ID, so on Equip, takes the armor from the dictionary, no need for casting.
#YungDeiza
[System.Serializable]
public class InventorySlot
{
public Item itemInSlot //Item is the base class
//First the slot is initialized with the normal JsonUtilities.FromJson, from my SaveLoad class. That works.
//I now have a reference to the Item so also Access to the item.ID
//This is how I tested JsonConvert.Serialize/Desirialize
public void SaveSlot()
{
if(item is Armor)
{
var json = JsonConvert.Serialize(item as Armor);
PlayerPrefs.SetString(item.ID, json);
}
}
public void LoadSlot()
{
if(PlayerPrefs.HasKey(item.ID))
{
var json = PlayerPrefs.GetString(item.ID);
var armor = JsonConvert.DeSerialize<Armor>(json);
item = new Armor(armor); //I have made a constructor for the Armor class that takes an Armor and "copies" it
}
}
}
That is my Save system:
public static class SaveLoad
{
public static UnityAction OnSaveGame;
public static UnityAction<SaveData> OnLoadGame;
public const string SaveDirectory = "/SaveData/";
private const string FileName = "Save.sav";
public static bool Save(SaveData saveData)
{
OnSaveGame?.Invoke();
string dir = Application.persistentDataPath + SaveDirectory;
GUIUtility.systemCopyBuffer = dir;
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
string json = JsonUtility.ToJson(saveData, true);
File.WriteAllText(dir + FileName, json);
return true;
}
public static void DeleteSavedData()
{
string fullPath = Application.persistentDataPath + SaveDirectory + FileName;
if (File.Exists(fullPath)) File.Delete(fullPath);
}
public static SaveData Load()
{
string fullPath = Application.persistentDataPath + SaveDirectory + FileName;
SaveData data = new SaveData();
if (File.Exists(fullPath))
{
string json = File.ReadAllText(fullPath);
data = JsonUtility.FromJson<SaveData>(json);
OnLoadGame?.Invoke(data);
}
else
{
Debug.LogWarning("Save File Does not exist");
}
return data;
}
}
And hereby what is the data is saved:
[System.Serializable]
public void PlayerData
{
//Banch of stats - intergers
public InventorySystem inventorySystem;
public PlayerData()
{
//Stats are initialized
inventorySystem = new InventorySystem(x); //I have a default inventory size of 24
}
}
[System.Serializable]
public void InventorySystem
{
public List<InventorySlots> inventorySlots;
public void InventorySystem(int size)
{
inventorySlots = new List<InventorySlots>(size);
}
}
As I said, when an enemy is killed, he drops an armor, and the armor class itself, just calculates random stats. The saveload method normally saves the Armor, and I can see it in my inventory, but I used a Debug.Log(item.GetType), and it always loads it as an Item class and not an Armor class. I ve read a lot, and as far as I can understand, Unity serialization and JsonUtilities, dont really support inheritance. So I m not sure where to look now.
Thank you in advance, I hope I helped understand my issue a bit better

Script in Unity responding incorrect values

I have been trying to use weatherbit.io API to access AQI information in my android application. The script AqiInfoScript is used to access the API and the Update AQI script is used to print the value out.
AqiInfoScript:
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using SimpleJSON;
public class AqiInfoScript : MonoBehaviour
{
private float timer;
public float minutesBetweenUpdate;
private float latitude;
private float longitude;
private bool locationInitialized;
public static string cityName;
public static double currentAqi;
private readonly string baseWeatherbitURL = "https://api.weatherbit.io/v2.0/current/airquality?";
private readonly string key = "*********************";
public void Begin()
{
latitude = GPS.latitude;
longitude = GPS.longitude;
locationInitialized = true;
}
void Update()
{
if (locationInitialized)
{
if (timer <= 0)
{
StartCoroutine(GetAqi());
timer = minutesBetweenUpdate * 60;
}
else
{
timer -= Time.deltaTime;
}
}
}
private IEnumerator GetAqi()
{
string weatherbitURL = baseWeatherbitURL + "lat=" + latitude + "&lon=" + longitude + "&key="
+ key;
UnityWebRequest aqiInfoRequest = UnityWebRequest.Get(weatherbitURL);
yield return aqiInfoRequest.SendWebRequest();
//error
if (aqiInfoRequest.isNetworkError || aqiInfoRequest.isHttpError)
{
Debug.LogError(aqiInfoRequest.error);
yield break;
}
JSONNode aqiInfo = JSON.Parse(aqiInfoRequest.downloadHandler.text);
cityName = aqiInfo["city_name"];
currentAqi = aqiInfo["data"]["aqi"];
}
}
UpdateAQI Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UpdateAQI : MonoBehaviour
{
public Text airquality;
//public Text coordinates;
private void Update()
{
airquality.text = "Current Aqi: " + AqiInfoScript.currentAqi.ToString();
}
}
Current Output : Current AQI: 0
Desired Output : Current AQI: 129.0000
Issue
As I see it the API returns for your request e.g. for lat=42, lon=-7 the following JSON
{
"data":[
{
"mold_level":1,
"aqi":54,
"pm10":7.71189,
"co":298.738,
"o3":115.871,
"predominant_pollen_type":"Molds",
"so2":0.952743,
"pollen_level_tree":1,
"pollen_level_weed":1,
"no2":0.233282,
"pm25":6.7908,
"pollen_level_grass":1
}
],
"city_name":"Pías",
"lon":-7,
"timezone":"Europe\/Madrid",
"lat":42,
"country_code":"ES",
"state_code":"55"
}
As you can see "data" is actually an array. You are treating it as a single value.
Behind the Scenes
Unfortunately the SmipleJson library you are using is quite error prone because it doesn't throw any exceptions for typos but rather silently fails and uses a default value.
You can see this e.g. in
public override JSONNode this[string aKey]
{
get
{
if (m_Dict.ContainsKey(aKey))
return m_Dict[aKey];
else
return new JSONLazyCreator(this, aKey);
}
....
}
and
public static implicit operator double(JSONNode d)
{
return (d == null) ? 0 : d.AsDouble;
}
Solution
It should rather be
cityName = aqiInfo["city_name"];
currentAqi = aqiInfo["data"][0]["aqi"].AsDouble;
Debug.Log($"cityName = {cityName}\ncurrentAqi = {currentAqi}");
In general I would suggest to use JsonUtility.FromJson and rather implement the required data structure in c# e.g.:
[Serializable]
public class Data
{
public int mold_level;
public double aqi;
public double pm10;
public double co;
public double o3;
public string predominant_pollen_type;
public double so2;
public int pollen_level_tree;
public int pollen_level_weed;
public double no2;
public double pm25;
public int pollen_level_grass;
}
[Serializable]
public class Root
{
public List<Data> data;
public string city_name;
public int lon;
public string timezone;
public int lat;
public string country_code;
public string state_code;
}
and then you would use
var aqiInfo = JsonUtility.FromJson<Root>(aqiInfoRequest.downloadHandler.text);
cityName = aqiInfo.city_name;
currentAqi = aqiInfo.data[0].aqi;
Debug.Log($"cityName = {cityName}\ncurrentAqi = {currentAqi}");
For me as said with the dummy values lat=42, lon=-7 both times it prints out as expected
cityName = Pías
currentAqi = 54
UnityEngine.Debug:Log(Object)
<GetAqi>d__18:MoveNext() (at Assets/Example.cs:109)
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)

Problem with serialization of object in unity

This is the class I want to serialize:
System.Serializable]
public class Save
{
public int turn, player;
public int build, attack, action;
public double resource, resourceProd;
public List<ObjectData> objectsData = new List<ObjectData>();
public Save(GameObject[] saveBuilding, GameObject[] saveUnit, int turn, int player, int build, int attack, int action, double resource, double resourceProd)
{
foreach(GameObject building in saveBuilding)
{
ObjectData data = new ObjectData();
data.position = building.transform.position;
data.building = building.GetComponent<Building>();
data.path = data.building.type[0] + data.building.level + data.building.branch;
objectsData.Add(data);
}
foreach(GameObject unit in saveUnit)
{
ObjectData data = new ObjectData();
data.position = unit.transform.position;
data.unit = unit.GetComponent<Unit>();
data.path = data.unit.type[0] + data.unit.level + data.unit.branch;
objectsData.Add(data);
}
this.turn = turn;
this.player = player;
this.build = build;
this.attack = attack;
this.action = action;
this.resource = resource;
this.resourceProd = resourceProd;
Debug.Log(objectsData.Count);
}
}
public class ObjectData
{
public Vector3 position;
public Building building;
public Unit unit;
public string path;
}
When I creat a instance of it with the method below, there are two element in objectsData according toDebug.Log(objectsData.Count);.
public static void SaveTurn()
{
turnSave = new Save(GameObject.FindGameObjectsWithTag("building"), GameObject.FindGameObjectsWithTag("unit"), turnCourt, Players.player, BuildDisplay.maxBuildAction, Attacking.maxAttackAction, Building.actions, Currency.resource, Building.totalprod);
solution.Add(JsonUtility.ToJson(turnSave));
Debug.Log(JsonUtility.ToJson(turnSave));
}
When I try the load the save Debug.Log(save.objectsData); returns null.
Why does this happens?
public static void LoadSave(string saves)
{
Save save = JsonUtility.FromJson<Save>(saves);
Currency.resource = save.resource;
Building.totalprod = save.resourceProd;
BuildDisplay.maxBuildAction = save.build;
Attacking.maxAttackAction = save.attack;
Building.actions = save.action;
Debug.Log(save.objectsData);
LoadObjects(save.objectsData);
RemoveObjects(GameObject.FindGameObjectsWithTag("building"));
RemoveObjects(GameObject.FindGameObjectsWithTag("unit"));
Turn.refresh();
}
public static void LoadObjects(List<ObjectData> gameObjects)
{
Debug.Log(gameObjects.Count);
foreach(ObjectData gameObject in gameObjects)
{
GameObject Prefab = Resources.Load(gameObject.path) as GameObject;
if(gameObject.building != null)
{
Building building = Prefab.GetComponent<Building>();
building = gameObject.building;
}
if(gameObject.unit != null)
{
Unit unit = Prefab.GetComponent<Unit>();
unit = gameObject.unit;
}
Instantiate(Prefab);
}
}
After making all class involved serializable Debug.Log(save.objectsData); no longer returns null and Debug.Log(gameObjects.Count); returns 2, but there is a null exception at Building building = Prefab.GetComponent<Building>(); even though there is a Building script on the Prefab.
First of all all classes you want to serialize need to have the attribute [Serializable] so also the ObjetData
[Serializable]
public class ObjectData
{
...
}
Then another "issue" here is that Unit and Building are both MonoBehaviour which derives from UnityEngine.Object. All types deriving from UnityEngine.Object are always only (de)serialized as instance references, never actually containing there according field values.
There are two similar ways around this.
Option A
Instead of serializing the Unit and Building directly you could rather serialize some dedicated data class that basically reflects the components field values but in a serializable container.
E.g. let's say your classes look like
public class Unit : MonoBehaviour
{
[SerializeField] private string _name;
[SerializeField] private int _id;
...
}
Then you could create a container like
[Serializable]
public class UnitData
{
public string Name;
public int ID;
}
And then in order to maintain capsulation and keep your fields private I would add according methods to your types themselves like e.g.
public class Unit : MonoBehaviour
{
[SerializeField] private string _name;
[SerializeField] private int _id;
public UnitContainer GetData()
{
return new UnitContainer()
{
Name = _name,
ID = _id;
}
}
public void SetData(UnitData data)
{
_name = data.Name;
_id = data.ID;
// ... Probably react to changed data
}
}
This way you could also store values that are usually not serialized like properties or private fields.
Same for the Building.
Then you could rather store these in your data like
[Serializable]
public class ObjectData
{
public Vector3 position;
public BuildingData building;
public UnitData unit;
public string path;
}
Then in your Save class you would make sure to create these data classes accordingly
foreach(var building in saveBuilding)
{
var data = new ObjectData()
{
position = building.transform.position,
building = building.GetComponent<Building>().GetData(),
path = data.building.type[0] + data.building.level + data.building.branch
};
objectsData.Add(data);
}
foreach(var unit in saveUnit)
{
var data = new ObjectData()
{
position = unit.transform.position,
unit = unit.GetComponent<Unit>().GetData(),
path = data.unit.type[0] + data.unit.level + data.unit.branch
};
objectsData.Add(data);
}
And for loading accordingly
foreach(var data in objectDatas)
{
var prefab = (GameObject) Resources.Load(data.path);
if(data.building != null)
{
var building = Instantiate (prefab).GetComponent<Building>();
building.SetData(data.building);
}
else if(data.unit != null)
{
var unit = Instantiate(prefab).GetComponent<Unit>();
unit.SetData(data.unit);
}
}
Option B
Actually quite similar but without the need for an extra data container class you could rather again (de)serialize your components directly using JSON like e.g.
public class Unit : MonoBehaviour
{
...
public string GetData()
{
return JsonUtility.ToJson(this);
}
public void SetData(string json)
{
JsonUtility.FromJsonOverwrite(json, this);
// ... React to changed data
}
}
This automatically includes all fields that can be serialized (are either public or tagged [SerializeField] and have a serializable type).
And then rather store these json strings like
[Serializable]
public class ObjectData
{
public Vector3 position;
public string building;
public string unit;
public string path;
}
The methods for save and load would be the same as for Option A above.

Cannot access a nonstatic member of outer type

public class ScoreManager : MonoBehaviour
{
public Text recordd;
string filePath;
string jsonString;
[System.Serializable]
public class Personaje
{
public string nombre;
public string profesion;
public int nivel;
public override string ToString ()
{
return string.Format ("{0}: {1} nivel {2}", nombre, profesion, nivel);
}
}
[System.Serializable]
public class ListaPersonajes
{
public List<Personaje> personajes;
public void Listar () {
//loop cada objeto lista
foreach (Personaje personaje in personajes) {
Debug.Log(personaje);
Debug.Log(personaje.nombre);
recordd.text = "" + personaje; //// THIS LINE ERROR WHY??
}
}
}
}
Error:
Error CS0038: Cannot access a nonstatic member of outer type 'ScoreManager' via nested type 'ScoreManager.ListaPersonajes'
Your'e trying to access the field recordd in the parent class.
The field isn't marked as static(and you probably don't want it to be static), and so the access is illegal.
You need to have an instance of ScoreManager to access the redordd
Edit:
public class Program
{
public static void Main()
{
ScoreManager scoreManager = InitializeMonoBehaviour(); // This is what you need to figure out, how to properly init the object.
List<Personaje> personajes = new List<Personaje>();
/*
fill personajes
*/
foreach (Personaje personaje in personajes)
{
Debug.Log(personaje);
Debug.Log(personaje.nombre);
scoreManager.AppendText(personaje.ToString());
}
}
}
public class ScoreManager : MonoBehaviour
{
public Text recordd;
string filePath;
string jsonString;
publlic void AppendText(string text)
{
this.recordd.text += text;
}
}
[System.Serializable]
public class Personaje
{
public string nombre;
public string profesion;
public int nivel;
public override string ToString ()
{
return string.Format ("{0}: {1} nivel {2}", nombre, profesion, nivel);
}
}
I removed ListaPersonajes class (currently I don't see a reason for it) and added AppendText(string text) method to ScoreManager class.
After you initialize the class, you can iterate through the list and append the text. I also replaced the ""+personaje syntax with a call to ToString()

c# class inherits from generics, method return type

I have generic class that should be a tree and I want to inherit the class like this:
public class Tree<T> {
private HashSet<Tree<T>> leaves;
private T data;
public Tree() {
leaves = new HashSet<Tree<T>>();
}
public Tree(T data) : this() {
this.data = data;
}
public T Data {
get {
return this.data;
}
set {
data = value;
}
}
public virtual Tree<T> findInLeaves(T data) {
foreach(Tree<T> leaf in leaves) {
if(leaf.Data.Equals(data)) {
return leaf;
}
}
return null;
}
}
public class ComboTree : Tree<IComboAction> {
private ComboMovement movement;
public ComboTree() : base() {
Movement = null;
}
public ComboTree(IComboAction action) : base(action) {
Movement = null;
}
public ComboMovement Movement {
get {
return this.movement;
}
set {
movement = value;
}
}
}
Putting data works well, but when I try to use method findInLeaves I always get null. I understand there is a problem with type casting, but why if ComboTree inherits Tree?
void readMove(IComboAction action) {
ComboTree leaf = (ComboTree)currentLeaf.findInLeaves(action);
}
Question is why and how to fix it?
Edit: I created console program, run it and it works. So this must be my engine problem!
public ComboTree(IComboAction action)
: base(action)
{
Movement = null; // <---- You are nulling Movement in the second constructor
}

Categories