9

Hi everyone i have a problem with my json serealization. I'm using the Json.NET package under Unity: I'm searching to make a Database, editable on my application and stored on my server through wwwForm and a php file. I have no problem to create it and to push it on the net. The problem is, when i load it, the database has a new entry at the end. The Database Class is this:

public class Database   {
    public List<Army> armies { get; set;}

    public Database() {
        armies = new List<Army>();
        armies.Add (new Army());
    }
}

public class Army
{
    public string faction { get; set;}
    public List<Unit> army { get; set;}

    public Army()
    {
        faction = "RED";
        army = new List<Unit>();
        army.Add (new Unit());
    }
}

public class Unit
{
    public string name { get; set;}
    public float fissionTimer { get; set;}
    public float HP { get; set;}
    public int shield { get; set;}
    public float strenght { get; set;}
    public float movSpeed { get; set;}
    public float attackSpeed { get; set;}
    public float farmAggro { get; set;}
    public int criticPossibility { get; set;}
    public int armorPenetration { get; set;}
    public bool isContagious { get; set;}
    public int contagePossibility { get; set;}
    public string imgName {get;set;}

    public Unit()
    {
        name = "standard";
        fissionTimer = 8;
        HP = 100;
        shield = 0;
        strenght = 10;
        movSpeed = 5;
        attackSpeed = 0.1f;
        farmAggro = 0.1f;
        criticPossibility = 0;
        armorPenetration = 0;
        isContagious = false;
        contagePossibility = 0;
        imgName = "Red";
    } 

    public Unit(string _name, float _fissionTimer, float _HP, int _shield, float _strenght, float _movSpeed, float _attackSpeed, 
                float _farmAggro, int _criticPossibility, int _armorPen, bool _iscontagious, int _contagePos, string _imgName)
    {
        name = _name;
        fissionTimer = _fissionTimer;
        HP = _HP;
        shield = _shield;
        strenght = _strenght;
        movSpeed = _movSpeed;
        attackSpeed = _attackSpeed;
        farmAggro = _farmAggro;
        criticPossibility = _criticPossibility;
        armorPenetration = _armorPen;
        isContagious = _iscontagious;
        contagePossibility = _contagePos;
        imgName = _imgName;
    }
}

to serialize and deserialize i use those 2 methods:

IEnumerator LoadFile()
{
    WWW www = new WWW(dbPath);

    yield return www;

    var _database = JsonConvert.DeserializeObject<Database> (www.text);

    db = _database;
    SendMessage ("loaded", SendMessageOptions.DontRequireReceiver);
}

IEnumerator SaveFile(Database db)
{
    WWWForm form = new WWWForm();
    string serialized = JsonConvert.SerializeObject (db);
    form.AddField("theDatabase", serialized);

    WWW www = new WWW(phpPath, form);

    yield return www;

    if (www.error == null)
        Debug.Log ("saved" + serialized);
    else
        Debug.LogError ("error saving database");
}

The result of using the default constructor, serialized and deserialized is this:

{
    "armies": [
        {
            "faction": "RED",
            "army": [
                {
                    "name": "standard",
                    "fissionTimer": 8,
                    "HP": 100,
                    "shield": 0,
                    "strenght": 10,
                    "movSpeed": 5,
                    "attackSpeed": 0.1,
                    "farmAggro": 0.1,
                    "criticPossibility": 0,
                    "armorPenetration": 0,
                    "isContagious": false,
                    "contagePossibility": 0,
                    "imgName": "Red"
                }
            ]
        },
        {
            "faction": "RED",
            "army": [
                {
                    "name": "standard",
                    "fissionTimer": 8,
                    "HP": 100,
                    "shield": 0,
                    "strenght": 10,
                    "movSpeed": 5,
                    "attackSpeed": 0.1,
                    "farmAggro": 0.1,
                    "criticPossibility": 0,
                    "armorPenetration": 0,
                    "isContagious": false,
                    "contagePossibility": 0,
                    "imgName": "Red"
                }
            ]
        }
    ]
}

There are 2 armies and 2 units. Where i am doing wrong? Thanks in advance

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • You are adding default items in constructors, try by removing them. – EZI Jul 18 '14 at 23:54
  • But i'm adding only one item. I tested like this: Database test = New Database(); //pseudocode test.armies.count? =1 (correct) serialize - save - load - deserialize (without any modify to the database test.armies.count? = 2.... Why 2? – Nicola Valcasara Jul 19 '14 at 10:07
  • See also: [Json.net deserializing list gives duplicate items](http://stackoverflow.com/q/13394401/10263) – Brian Rogers Oct 08 '14 at 18:34
  • FWIW I fixed it by using a HashSet so that no duplicate values are allowed (in my case, enums). – kamranicus Jan 08 '16 at 17:17

2 Answers2

20

The reason this is happening is due to the combination of two things:

  1. Your class constructors automatically add default items to their respective lists. Json.Net calls those same constructors to create the object instances during deserialization.
  2. Json.Net's default behavior is to reuse (i.e. add to) existing lists during deserialization instead of replacing them.

To fix this, you can either change your code such that your constructors do not automatically add default items to your lists, or you can configure Json.Net to replace the lists on deserialization rather than reusing them. The latter by can be done by changing the ObjectCreationHandling setting to Replace as shown below:

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ObjectCreationHandling = ObjectCreationHandling.Replace;

var database = JsonConvert.DeserializeObject<Database>(www.text, settings);
Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • Thx a lot, i solved it just removing the .add on each constructor, but you teach me how json serialization works ;) useful and nice answer, thx again – Nicola Valcasara Jul 20 '14 at 16:00
3

Best way would be to configure JSON.Net to replace the default values by

JsonSerializerSettings   jsSettings =  new JsonSerializerSettings
{
  ObjectCreationHandling = ObjectCreationHandling.Replace,
};

JsonConvert.DeserializeObject<Army>(jsonString, jsSettings);