This question already has answers here:
Serialize and Deserialize Json and Json Array in Unity
(9 answers)
Closed 5 years ago.
{
"check":"success",
"stats":{
"2":{
"rank":1,
"score":"2000",
"name":"Muhammad"
},
"3":{
"rank":1,
"score":"2000",
"name":"Ramsay"
}}}
this is my json string. I want to use "stats" as a list to check how many entries it has. Then I need to get the rank, name, score etc from each entry.
My code is,
var result = Json.Deserialize(jsontest) as Dictionary<string,object>;
object st;
var rankholders = new List<object>();
if (result.TryGetValue("stats", out st))
{
rankholders = (List<object>)(((Dictionary<string, object>)st)["stats"]);
foreach (object obj in rankholders)
{
var tempDict = ((Dictionary<string,object>)(rankholders[0]));
WeeklyStatsItem tempRow = new WeeklyStatsItem ();
tempRow.rank = (string)tempDict["rank"];
tempRow.name = (string)tempDict["name"];
tempRow.score = (string)tempDict["score"];
weeklyScoreList.Add (tempRow);
}
}
But I get keynotfound exception. Any idea how to parse such a json loop?
To me it looks like you have an object which contains a property "check" and a dictionary called "stats". If you will make your own class having this structure, you will be able to deserialize json to this type of object
Something like:
class Stats
{
public int rank;
public int score;
public string name;
}
class ResultContainer
{
public string checked;
public Dictionary<int,Stats>stats;
}
And you should deserialize as ResultContainer.
Your Json is NOT valid because it was throwing error when stats is made to be a List or array. I was able to re-create a valid Json with the code below:
I want to use "stats" as a list to check how many entries it has
WeeklyStatsItem recreation = new WeeklyStatsItem();
recreation.check = "success";
recreation.stats = new List<Stats>();
Stats st1 = new Stats();
st1.rank = 1;
st1.score = "2000";
st1.name = "Muhammad";
recreation.stats.Add(st1);
Stats st2 = new Stats();
st2.rank = 1;
st2.score = "2000";
st2.name = "Ramsay";
recreation.stats.Add(st2);
Debug.Log(JsonUtility.ToJson(recreation));
The new generated valid Json is:
{"check":"success","stats":[{"rank":1,"score":"2000","name":"Muhammad"},{"rank":1,"score":"2000","name":"Ramsay"}]}
Now to answer your question:
I want to use "stats" as a list to check how many entries it has.
string jsonTest = "{\"check\":\"success\",\"stats\":[{\"rank\":1,\"score\":\"2000\",\"name\":\"Muhammad\"},{\"rank\":1,\"score\":\"2000\",\"name\":\"Ramsay\"}]}";
WeeklyStatsItem weeklyIt = JsonUtility.FromJson<WeeklyStatsItem>(jsonTest);
Debug.Log(weeklyIt.stats.Count);
Then I need to get the rank, name, score etc from each entry
string jsonTest = "{\"check\":\"success\",\"stats\":[{\"rank\":1,\"score\":\"2000\",\"name\":\"Muhammad\"},{\"rank\":1,\"score\":\"2000\",\"name\":\"Ramsay\"}]}";
WeeklyStatsItem weeklyIt = JsonUtility.FromJson<WeeklyStatsItem>(jsonTest);
Debug.Log("CHECK: " + weeklyIt.check);
for (int i = 0; i < weeklyIt.stats.Count; i++)
{
Debug.Log("STATS INDEX: " + i);
Debug.Log("RANK: " + weeklyIt.stats[i].rank);
Debug.Log("NAME: " + weeklyIt.stats[i].name);
Debug.Log("SCORE: " + weeklyIt.stats[i].score);
}
Your Json Classes:
[Serializable]
public class Stats
{
public int rank;
public string score;
public string name;
}
[Serializable]
public class WeeklyStatsItem
{
public string check;
public List<Stats> stats;
}
Since you're keys have semantic meaning and don't map to variable names you'll have to go a bit further than the basic examples that assume your json objects map neatly to a c# object, i.e. that they have a fixed schema.
Basically you'll have to look at each entry in the stats dict and pass both the key and the underlying values to your domain object constructor.
I'm not super familiar with MiniJSON, but I've banged together a working example. I suspect there is a more idiomatic way of doing this, e.g. using generics.
Assets/Editor/ParserTest.cs
using NUnit.Framework;
public class ParserTest {
[Test]
public void TestThatTwoPlayersAreInTestResponse()
{
string testResponse = "{ \"check\":\"success\", \"stats\":{ \"2\":{ \"rank\":1, \"score\":\"2000\", \"name\":\"Muhammad\" }, \"3\":{ \"rank\":1, \"score\":\"2000\", \"name\":\"Ramsay\" } } }";
Assert.AreEqual(MiniJsonParsingExample.parseResponse(testResponse).Count, 2);
}
}
Assets/Scripts/MiniJsonParsingExample.cs
using System.Collections.Generic;
using Facebook.MiniJSON;
public class MiniJsonParsingExample
{
public static List parseResponse(string responseText)
{
var resultDict = Json.Deserialize(responseText) as Dictionary;
var players = new List();
if (resultDict.ContainsKey("stats"))
{
var playerDict = resultDict["stats"] as Dictionary;
foreach (string playerId in playerDict.Keys)
{
var psuedoPlayer = playerDict[playerId] as Dictionary;
string playerName = psuedoPlayer["name"] as string;
long playerRank = (long) psuedoPlayer["rank"];
string playerScore = psuedoPlayer["score"] as string;
players.Add(new Player(playerId, playerName, playerRank, playerScore));
}
}
return players;
}
public class Player
{
string id;
string name;
long rank;
string score;
public Player(string id, string name, long rank, string score)
{
this.id = id;
this.name = name;
this.rank = rank;
this.score = score;
}
}
}
Related
I am currently making an application which tracks information on players and monsters for a tabletop game.
Currently I've got classes for "Monsters". This class contains information such as its name, maxHP, speed, attacks etc. I've managed to make a Database which contains the default values for each type of monster. What I currently need to do is make it possible to change things such as name (Monster > Monster 1, Monster 2 etc), change its HP, and some other things.
I understand that I need to make a copy of such, but I am uncertain on how to do this.
What I currently tried is the following:
public class DatabaseService
{
public List<Player> Players { get; set; }
public List<Monster> AllMonsters { get; set; }
public List<Monster> ActiveMonsters = new List<Monster>();
public bool RollForHP = false;
//Main Database Service
public DatabaseService()
{
Players = GetPlayers();
AllMonsters = GetAllMonsters();
}
public void DoStuff()
{
AddMonsterByName("Goblin", 2);
AddMonsterByName("Adult White Dragon", 1);
AddMonsterByName("Duergar", 4);
foreach (Monster monster in ActiveMonsters) { Console.WriteLine(monster.Name); }
}
//Converts the .json list with all players to Classes, which are then stored in the list "Players" if the "IsInParty" is true
private List<Player> GetPlayers()
{
var path = #"C:\Users\MyName\source\repos\DndAdvancedInitiativeTracker\Logic\Database\Players.json";
var json = File.ReadAllText(path);
var players = JsonConvert.DeserializeObject<List<Player>>(json);
List<Player> inPartyPlayers = new List<Player>();
foreach (var player in players)
{
if (player.IsInParty == true) { inPartyPlayers.Add(player); }
}
return inPartyPlayers;
}
//Converts the .json list with all monsters to Classes, which are then stored in the list "AllMonsters"
private List<Monster> GetAllMonsters()
{
var path = #"C:\Users\MyName\source\repos\DndAdvancedInitiativeTracker\Logic\Database\Monsters.json";
var json = File.ReadAllText(path);
var monsters = JsonConvert.DeserializeObject<List<Monster>>(json);
return monsters;
}
//Adds a given monster to the "ActiveMonsters" list
public void AddMonsterByName(string monsterName, int amountOfMonsters)
{
for (int i = 0; i < amountOfMonsters; i++)
{
List<Monster> DatabaseCopy = AllMonsters.Clone();
DatabaseCopy = AllMonsters;
Monster monster = DatabaseCopy.Find(x => x.Name == monsterName);
Console.WriteLine(monster.Name);
var number = CheckIfNameExistsInList(monsterName);
monster.Name = monsterName + " " + (number + i).ToString();
ActiveMonsters.Add(monster);
}
}
private int CheckIfNameExistsInList(string monsterName)
{
var counter = 1;
foreach (var monster in ActiveMonsters)
{
if (monster.Name.Contains(monsterName))
{
counter += 1;
}
}
return counter;
}
}
In the "DoStuff" Method, I try to add 2 goblins, then a dragon, then a goblin again. The first goblin is named to "Goblin 1" correctly, but the second loop fails, because the AllMonsters' name for goblins is now "Goblin 1" because of the reference type, therefore, the second "Goblin" search in AllMonsters is never found, and returns null.
I'm not sure why you're copying your database (and doing so for every iteration of a for loop which is quite wasteful), but your current check code CheckIfNameExistsInList will always return 1 even if there are no matches in the list.
You can simplify your AddMonstersByName (and use a simple check for previous monster entries) as follows:
public void AddMonstersByName(string name, uint number = 1)
{
var count = AllMonsters.Count(x => x.Name.Contains(name));
for (var i = 1; i <= number; i++)
{
var num = count + i;
AllMonsters.Add(new Monster(){Name= name+num.ToString()});
}
}
This was tested in a simple Console App:
var dataService = new DataService();
dataService.AddMonstersByName("Goblin", 2);
dataService.AddMonstersByName("Dragon", 2);
dataService.AddMonstersByName("Goblin", 2);
foreach (var monster in dataService.AllMonsters)
{
Console.WriteLine($"{monster.Name}");
}
where
public class DataService
{
public List<Monster> AllMonsters = new List<Monster>();
public void AddMonstersByName(string name, uint number = 1)
{
var count = AllMonsters.Count(x => x.Name.Contains(name));
for (var i = 1; i <= number; i++)
{
var num = count + i;
AllMonsters.Add(new Monster(){Name= name+num.ToString()});
}
}
}
public class Monster
{
public string Name { get; set; }
}
Consider the following mutable object:
class SomePoco
{
public int Id{get;set;}
public string Name{get;set;}
}
Let's round trip it through Json.NET:
var p=new SomePoco{Id=4,Name="spender"};
var json=JsonConvert.SerializeObject(p);
var pr = JsonConvert.DeserializeObject<SomePoco>(json);
Console.WriteLine($"Id:{pr.Id}, Name:{pr.Name}");
All is good.
Now, let's make out POCO immutable and feed values via a constructor:
class SomeImmutablePoco
{
public SomeImmutablePoco(int id, string name)
{
Id = id;
Name = name;
}
public int Id{get;}
public string Name{get;}
}
... and round-trip the data again:
var p = new SomeImmutablePoco(5, "spender's immutable friend");
var json = JsonConvert.SerializeObject(p);
var pr = JsonConvert.DeserializeObject<SomeImmutablePoco>(json);
Console.WriteLine($"Id:{pr.Id}, Name:{pr.Name}");
Still good.
Now, let's make a small change to our immutable class by renaming a constructor parameter:
class SomeImmutablePoco
{
public SomeImmutablePoco(int pocoId, string name)
{
Id = pocoId;
Name = name;
}
public int Id{get;}
public string Name{get;}
}
then:
var p = new SomeImmutablePoco(666, "diabolo");
var json = JsonConvert.SerializeObject(p);
var pr = JsonConvert.DeserializeObject<SomeImmutablePoco>(json);
Console.WriteLine($"Id:{pr.Id}, Name:{pr.Name}");
Oh dear... It looks like Json.NET is doing some reflective magic over the names of our constructor parameters and matching them to property names in our POCO/json. This means that our freshly deserialized object doesn't get an Id assigned to it. When we print out the Id, it's 0.
This is bad, and particularly troublesome to track down.
This problem might exist in a large collection of POCOs. How can I automate finding these problem POCO classes?
Here is the code that finds such classes using reflection:
var types = new List<Type>() { typeof(SomeImmutablePoco) }; // get all types using reflection
foreach (var type in types)
{
var props = type.GetProperties(bindingAttr: System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
foreach (var ctor in type.GetConstructors())
{
foreach (var param in ctor.GetParameters())
{
if (!props.Select(prop => prop.Name.ToLower()).Contains(param.Name.ToLower()))
{
Console.WriteLine($"The type {type.FullName} may have problems with Deserialization");
}
}
}
}
You can map your json property like that:
class SomeImmutablePoco
{
public SomeImmutablePoco(int pocoId, string name)
{
Id = pocoId;
Name = name;
}
[JsonProperty("pocoId")]
public int Id { get; }
public string Name { get; }
}
Suppose I have a list of Robot class [List< Robot> myList=new List< Robot>()]. Each Robot has a name and id depending on its colour. Now randomly pick values from the list and give an output of how many Robots of each colour are there on your list.
(N.B. Consider you have only 3 colored Robot[Yellow,green, red])
my code:
public class Test : MonoBehaviour
{
private void Start()
{
List<Robot> myList = new List<Robot>();
List<string> robotList = new List<string>();
robotList.Add("yellow");
robotList.Add("green");
robotList.Add("red");
int someNum = Random.Range(0, robotList.Count);
string robotNumber = robotList[someNum];
robotList.RemoveAt(someNum);
Robot robot;
int id = 0;
robot = new Robot(robotNumber, id);
Debug.Log(robot);
id++;
}
}
public class Robot
{
public string name;
public int id;
public Robot(string name, int id)
{
this.name = name;
this.id = id;
}
}
but this not work maybe.. actually I don't understand what actually my output is...
Not sure to really understand what you're asking for: if it's only about the meaning of the Debug.Log(robot); output, check for #Smartis answer as it answers it perfectly :)
Otherwise, I feel like you wanted to populate a List<Robot> with random picked names. In this case you need to use a loop: Start() method is only called once on start (as its name suggest). If you need to populate a list with random picked colors/names and then display how many of each colors/names are in the list you can do it as follow:
public class Test : MonoBehaviour
{
private void Start()
{
List<Robot> robotsList = new List<Robot>();
List<string> namesList = new List<string>();
namesList.Add("yellow");
namesList.Add("green");
namesList.Add("red");
PopulateRobotsList();
DisplayRobotsListContent();
}
private void PopulateRobotsList()
{
for(int id = 0; id < 100; id++)
{
string robotName = namesList[Random.Range(0, namesList.Count)];
robotsList.Add(new Robot(robotName, id));
//Debug.Log(robotsList[robotsList.Count - 1]);
}
}
private void DisplayRobotsListContent()
{
int[] robotsNamesCount = new int[namesList.Count];
for (int i = 0; i < robotsList.Count; i++)
{
robotsNamesCount[namesList.IndexOf(robotsList[i].name)] += 1;
}
for (int i = 0; i < namesList.Count; i++)
{
Debug.Log("Robot(s) named \"" + namesList[i] + "\" : " + robotsNamesCount[i]);
}
}
}
public class Robot
{
public string name;
public int id;
public Robot(string name, int id)
{
this.name = name;
this.id = id;
}
}
Please note I changed some variable names as I found it really hard to understand with the one you provided (ex: robotsList to store the potential colors/names of the robots is a weird choice of name :) ).
Hope this helps,
Your random pick works fine (even when your code is a little bit confusing). I guess your problem is, you don't understand the output of Debug.Log(robot);.
actually I don't understand what actually my output is... - OP
What does Debug.Log() print?
According to the Unity3D Documentation for this function will converted the given object to be to string representation for display. This means simply the return value of ToString() method on your Object will printed.
So let's have look at the Object.ToString() method and it's behavior in the MSDN Documentation.
Default implementations of the Object.ToString method return the fully qualified name of the object's type. - MSDN
So, your output in the Unity Log will be the Type Definition of your object.
Now, how to get useful information?
Just override the default ToString() method of your Robot class to something like this:
public class Robot
{
public string name;
public int id;
public Robot(string name, int id)
{
this.name = name;
this.id = id;
}
// Here start's the magic!
public override string ToString()
{
return string.Format("Robot -> Id:'{0}' Name:'{1}'", id, name);
}
}
I have a dictionary which stores members of a 'Skiing' tournament. It also stores there scores. What I want to do is be able to find and display the top 3 scores of the members. I was just wondering what the best way would be to approach this as I am stuck at the moment. The following is the dictionary and how a member is added:
public static Dictionary<string, Skier> Skiers = new Dictionary<string, Skier>();
static int income;
string lodgeName;
public SkiLodge(string newLodgeName)
{
newLodgeName = lodgeName;
Skiers = new Dictionary<string, Skier>();
}
static int newNumber = 1;
//ADD SKIER
public Skier AddSkier(string inName, string inAddress, int inScore)
{
string newNumberString = newNumber.ToString();
Skier result = new Skier(newNumberString, inName, inAddress, inScore);
newNumber = newNumber + 1;
Skier S = new Skier(newNumberString, inName, inAddress, inScore);
Skiers.Add(newNumberString, S);
income = income + 100;
return result;
}
I assumed that you have a property in Skier called Score, here how can you achieve your goal.
//Your dictionary must have at least 3 entries.
var orderedTopThree = Skiers.OrderByDescending(s => s.Value.Score).Take(3);
Either of these methods added to your SkiLodge class, will get you what you're looking for.
This will grab your top X KeyValuePairs with the Skier object being the Value property of the KeyValuePair.
public List<KeyValuePair<string,Skier>> GetTopSkiers(int howMany)
{
return Skiers.OrderByDescending(kvp => kvp.Value.Score).Take(howMany).ToList();
}
This will grab your top X Skiers
public List<Skier> GetTopSkiers(int howMany)
{
return Skiers.OrderByDescending(kvp => kvp.Value.Score).Take(howMany).Select(kvp => kvp.Value).ToList();
}
Both methods use the OrderByDescending Linq method, which uses a lambda expression as a selector for the sorting comparison kvp => kvp.Value.Score. Which in this case is saying, foreach kvp (KeyValuePair) in this dictionary, sort them by the Value property, which in this case is the Skier object, and use the Skier object's Score as the value to sort by.
Take will take, up to x values from an Enumerable.
Select then returns an Enumerable resulting from the lambda function passed in. In this case, kvp => kvp.Value returns an Enumerable of Skier objects, instead of a list of the KeyValuePair objects.
Either use a SortedDictionary or enumerate through the keys of dictionary or this code
public class Skier
{
public static Dictionary<string, Skier> Skiers = new Dictionary<string, Skier>();
public int inScore { get; set; }
public Skier()
{
int[] highscore = Skiers.AsEnumerable().OrderByDescending(x => ((Skier)x.Value).inScore).Take(3).Select(y => ((Skier)y).inScore).ToArray();
}
}
You may use LINQ to achieve. Here is one sample query:
Skiers.OrderByDescending(e=>e.Value.inScore).Take(3).ToList();
Using System.Linq, the following code will get you the best 3 Skiers by score:
public List<Skier> GetTop3()
{
var list = Skiers.OrderByDescending(sk=> sk.Value.Score).Take(3).ToList();
return list;
}
I have tried to replicate the behavior and fixed some of the issues in above code. In AddSkier you dont need to create 2 objects.
FetchTopThree() will give you the top 3 results. You can then display the results.
using System.Collections.Generic;
using System.Linq;
public class SkiLodge
{
public static Dictionary<string, Skier> Skiers = new Dictionary<string, Skier>();
static int income;
string lodgeName;
public SkiLodge(string newLodgeName)
{
this.lodgeName = newLodgeName;
}
static int newNumber = 1;
//ADD SKIER
public Skier AddSkier(string inName, string inAddress, int inScore)
{
string newNumberString = newNumber.ToString();
newNumber = newNumber + 1;
Skier skier= new Skier(newNumberString, inName, inAddress, inScore);
Skiers.Add(newNumberString, skier);
income = income + 100;
return skier;
}
public List<Skier> FetchTopThree()
{
return Skiers.Values.OrderByDescending(s => s.InScore).Take(3).ToList();
}
}
public class Skier
{
public string NewNumberString { get; }
public string InName { get; }
private readonly string inAddress;
public int InScore { get; }
public Skier(string newNumberString, string inName, string inAddress, int inScore)
{
this.NewNumberString = newNumberString;
this.InName = inName;
this.inAddress = inAddress;
this.InScore = inScore;
}
}
I am adding a function to keep track of scores for a small game I made.
I want to get the top 5 scores (including name for that score) from a file that contains the scores.
The format of the saved scores is:
[name]-[score]
The scores and names are stored in 2 lists, which I parse this way:
string scores = File.ReadAllText(Environment.GetEnvironmentVariable("TEMP") + "/scores");
string[] splitscores = scores.Split('\n');
foreach (string entry in splitscores)
{
string replace = entry.Replace("[", "").Replace("]", "");
string[] splitentry = replace.Split('-');
if (splitentry.Count() > 1)
{
scorenames.Add(splitentry[0]);
scorelist.Add(Int32.Parse(splitentry[1]));
}
}
Then I retrieve #1 player by using:
int indexMax
= !scorelist.Any() ? -1 :
scorelist
.Select((value, index) => new { Value = value, Index = index })
.Aggregate((a, b) => (a.Value > b.Value) ? a : b)
.Index;
lblhighscore1.Text = "#1: " + scorelist[indexMax] + " by " + scorenames[indexMax];
How can I set the remaining 4 players assuming this is my scorelist:
[broodplank]-[12240]
[flo]-[10944]
[bill]-[11456]
[tony]-[9900]
[benji]-[7562]
I've figured I could do a descending sort of the score list, but that wouldn't cover the changes in the indexes of the usernames list, what is the best approach for this?
Best approach? Don't use parallel collections anti-pattern.
Instead of having 2 lists, create a class that can hold both the name and the score together
class Score
{
public string Name { get; private set; }
public int Score { get; private set; }
public Score(string name, int score)
{
Name = name;
Score = score;
}
}
and have just one list
List<Score> scores = new List<Score>();
foreach (string entry in splitscores)
{
string replace = entry.Replace("[", "").Replace("]", "");
string[] splitentry = replace.Split('-');
if (splitentry.Count() > 1)
{
scores.Add(new Score(splitentry[0], Int32.Parse(splitentry[1]))
}
}
You can easily order by one property and because the whole object will be reordered you'll keep the names in the right order without any additional code:
topScores = scores.OrderByDescending(x => x.Score).Take(5);
In addition to MarcinJuraszeks useful answer, some small things that I came across using his solution which I decided to share.
First problem was with the class which threw me the following error
'Score': member names cannot be the same as their enclosing type
Changing the case of "s" fixed it
class Score
{
public string Name { get; private set; }
public int score { get; private set; }
public Score(string name, int Score)
{
Name = name;
score = Score;
}
}
And calling the individual values can be done with Linq
string numberOne = topScores.Skip(0).First().score
string numberTwo = topScores.Skip(1).First().score
and so on