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
The reason this is happening is due to the combination of two things:
Your class constructors automatically add default items to their respective lists. Json.Net calls those same constructors to create the object instances during deserialization.
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);
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);
Related
I am totally not getting this, because I have used this library in Xamarin apps for several years.
I have this base class that contains properties common in all db items:
public class BaseItem
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; } = 0; // SQLite ID
public long CreatedTimeSeconds { get; set; } = DateTime.Now.ToUnixTimeSeconds();
public long ModifiedTimeSeconds { get; set; } = DateTime.Now.ToUnixTimeSeconds();
}
Now, I derive from it:
[Table("CategoryTable")]
public class Category : BaseItem
{
public int CategoryTypeID { get; set; } = (int)CategoryType.Invalid;
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
}
Here's a simplified version of what I'm seeing:
public class DBWorld
{
ISQLiteService SQLite { get { return DependencyService.Get<ISQLiteService>(); } }
private readonly SQLiteConnection _conn;
public DBWorld()
{
_conn = SQLite.GetConnection("myapp.sqlite");
}
public void TestThis()
{
_conn.CreateTable<Category>();
var category = new Category();
category.Name = "This Should Work";
int recCount = connection.Insert(category);
// at this point recCount shows as 1, and category.ID shows as zero.
// I thought Insert was supposed to set the autoincrement primary key
// regardless, it should be set in the database, right? So...
var categoryList = connection.Query<Category>($"SELECT * FROM {DBConstants.CategoryTableName}");
// at this point categoryList[0] contains all the expected values, except ID = 0
}
}
I am obviously missing something, but for the life of me, I can't figure out what...
Like so many other bizarre things that happen in the Visual Studio Xamarin world, when I went back later, this worked the way all of us expect. I guess Visual Studio was just tired and needed to be restarted.
I have a List<Semesters> Object and Semesters class has Semester as String[] type. Below is my code which has error where Semester[1]= item.Semester. What is the correct syntax?
public class CourseList
{
public int? Prog_id { get; set; }
public List<Semesters> Series{ get; set; }
}
public class Semesters
{
public string[] Semester { get; set; }
public string Subject { get; set; }
public int Credit {get; set;}
}
[HttpGet]
[AllowAnonymous]
public CourseList GetCourseList(int Progid)
{
using (var context = new DataContext())
{
CourseList Obj = new CourseList();
List<Semesters> CObj = new List<Semesters>();
var qry = (from a in context.Courses
where a.Prog_id == Progid
orderby a.Semester
select a).ToList();
foreach (var item in qry)
{
Obj.Prog_id = item.Prog_id;
if (item.Semester.ToString() == "1st Semester")
{
CObj.Add(new Semesters {Semester[1]=item.Semester, Subject = item.Subject.ToString(), Coursetitle= item.Coursetitle });
}
if (item.Semester.ToString() == "2nd Semester")
{
CObj.Add(new Semesters { Semester[2] = item.Semester, Subject = item.Subject.ToString(), Coursetitle = item.Coursetitle });
}
Obj.Series = CObj;
}
return Obj;
}
You say Semester is an array of strings, so in this statement
Semester[1] = item.Semester
Semester is an array and item.Semester is an array.
Semester[1] should be assigned a string, but you are assigning it an array. You'll need to change it to (possibly...)
// assign a value from the item.Semester array.
Semester[1] = item.Semester[n]
That's one possibility w/o more info.
I want to modify my json.NET serializer to add the $type property only to the objects which implements a given interface but not to any property or nested objects.
With TypeNameHandling.Auto (default)
{
"PropertyA": 123,
"PropertyB": "foo",
"PropertyC": [1, 2, 3, 4]
}
With TypeNameHandling.All
{
"$type": "JsonNetTypeNameHandling.TestEvent, jsonNetTypeNameHandling",
"PropertyA": 123,
"PropertyB": "foo",
"PropertyC": {
"$type": "System.Collections.Generic.List`1[[System.Int32, mscorlib]], mscorlib",
"$values": [1, 2, 3, 4 ]
}
}
What I want
{
"$type": "JsonNetTypeNameHandling.TestEvent, jsonNetTypeNameHandling",
"PropertyA": 123,
"PropertyB": "foo",
"PropertyC": [1, 2, 3, 4]
}
I am experimenting with a custom ContractResolver but I don't get it to work:
class Program
{
static void Main(string[] args)
{
var serializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
ContractResolver = new EnableTypeNameHandlingAllOnlyForEvents(),
Formatting = Formatting.Indented
};
var event1 = new TestEvent() { PropertyA = 123, PropertyB = "foo", PropertyC = new List<int> { 1, 2, 3, 4 } };
string event1Serialized = JsonConvert.SerializeObject(event1, serializerSettings);
Console.WriteLine(event1Serialized);
Console.ReadLine();
}
}
public interface IEvent
{
}
public class TestEvent : IEvent
{
public int PropertyA { get; set; }
public string PropertyB { get; set; }
public List<int> PropertyC { get; set; }
}
public class EnableTypeNameHandlingAllOnlyForEvents : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var x = base.CreateObjectContract(objectType);
if (typeof(IEvent).IsAssignableFrom(x.UnderlyingType))
{
// What to do to tell json.NET to add $type to instances of this (IEvent) type???
}
return x;
}
}
If you require the "$type" property on your root object and are OK with it appearing on nested polymorphic objects and arrays if required, use the following overload along with TypeNameHandling.Auto: JsonConvert.SerializeObject(Object, Type, JsonSerializerSettings).
From the docs:
public static string SerializeObject(
Object value,
Type type,
JsonSerializerSettings settings
)
type
Type: System.Type
The type of the value being serialized. This parameter is used when TypeNameHandling is Auto to write out the type name if the type of the value does not match. Specifing the type is optional.
I.e., do:
var serializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
Formatting = Formatting.Indented
};
var event1Serialized = JsonConvert.SerializeObject(event1, typeof(IEvent), serializerSettings);
If you require "$type" on the root object and will not accept it on nested polymorphic objects and arrays even if otherwise required, you will need to use TypeNameHandling.All along with a custom contract resolver that sets JsonContainerContract.ItemTypeNameHandling = TypeNameHandling.None:
public class SuppressItemTypeNameContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
var containerContract = contract as JsonContainerContract;
if (containerContract != null)
{
if (containerContract.ItemTypeNameHandling == null)
containerContract.ItemTypeNameHandling = TypeNameHandling.None;
}
return contract;
}
}
Then use it like:
static IContractResolver suppressItemTypeNameContractResolver = new SuppressItemTypeNameContractResolver();
var serializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All,
ContractResolver = suppressItemTypeNameContractResolver,
// Other settings as required.
TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
Formatting = Formatting.Indented
};
var event1Serialized = JsonConvert.SerializeObject(event1, serializerSettings);
Notes:
Be aware of this caution from the Newtonsoft docs:
TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.
For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json, How to configure Json.NET to create a vulnerable web API, and Alvaro Muñoz & Oleksandr Mirosh's blackhat paper https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf
You may want to statically cache the contract resolver for best performance.
Simpler solution is to override the TypeHandling on property level
public class TestEvent : IEvent
{
public int PropertyA { get; set; }
public string PropertyB { get; set; }
[JsonProperty(TypeNameHandling = TypeNameHandling.None)]
public List<int> PropertyC { get; set; }
}
I'm new to code-first, but love it. I have an inheritance structure: the nodes are coupled by a connection class. However, I suspect due to the PK, FK relationship between the base and derived classes there is is primary key violation.
Violation of PRIMARY KEY constraint 'PK_dbo.Connections'. Cannot insert duplicate key in object 'dbo.Connections'
So, not being a database Mack-O-Grady, I'm wondering if this is doable?
public abstract class Node
{
public Guid Id { get; set; }
}
[Table("Node1")]
public class Node1 : Node { }
[Table("Node2")]
public class Node2 : Node { }
[Table("Node3")]
public class Node3 : Node { }
public class Connection
{
public Guid Id { get; set; }
public Node Parent { get; set; }
public int ParentPort { get; set; }
public Node Child { get; set; }
}
public class TestContext : DbContext
{
public TestContext() : base("TST")
{
var ensureDLLIsCopied = System.Data.Entity.SqlServer.SqlProviderServices.Instance;
}
public DbSet<Node> Nodes { get; set; }
public DbSet<Connection> Connections { get; set; }
}
class Program
{
static void Main(string[] args)
{
var nodes = new List<Node>();
using (var context = new TestContext())
{
var n1 = new Node1
{
Id = Guid.NewGuid(),
};
nodes.Add(n1);
context.Nodes.Add(n1);
var n2 = new Node2
{
Id = Guid.NewGuid(),
};
nodes.Add(n2);
context.Nodes.Add(n2);
var n3 = new Node2
{
Id = Guid.NewGuid(),
};
nodes.Add(n3);
context.Nodes.Add(n3);
var c1 = new Connection { Parent = n1, ParentPort = 0, Child = n2 };
context.Connections.Add(c1);
var c2 = new Connection { Parent = n2, ParentPort = 0, Child = n3 };
context.Connections.Add(c2);
context.SaveChanges(); // exception when saving????
Console.ReadLine();
}
}
}
The answer is because the Id property (primary key wasn't being set)
var c1 = new Connection { Id = Guid.NewGuid(), Parent = n1, ParentPort = 0, Child = n2 };
context.Connections.Add(c1);
var c2 = new Connection { Id = Guid.NewGuid(), Parent = n2, ParentPort = 0, Child = n3 };
context.Connections.Add(c2);
How should I Map an object with a nullable field? I guess I must turn the nullable field into a non-nullable version, and it's that step that I stumble upon.
What is the proper way to map nullable properties?
public class Visit {
public string Id { get; set; }
public int? MediaSourceId { get; set; }
}
public class MapReduceResult
{
public string VisitId { get; set; }
public int MediaSourceId { get; set; }
public string Version { get; set; }
public int Count { get; set; }
}
AddMap<Visit>(
visits =>
from visit in visits
select new
{
VisitId = visit.Id,
MediaSourceId =
(visit.MediaSourceId.HasValue)
? visit.MediaSourceId
: UNUSED_MEDIASOURCE_ID,
Version = (string) null,
Count = 1
});
This doesn't work! In fact; this Map is completely ignored, while the other Maps work fine, and they are in the end Reduced as expected.
Thanks for helping me!
Below is a newly added test case that fails with a "Cannot assign <null> to anonymous type property". How am I supposed to get this flying with the least amount of pain?
[TestFixture]
public class MyIndexTest
{
private IDocumentStore _documentStore;
[SetUp]
public void SetUp()
{
_documentStore = new EmbeddableDocumentStore {RunInMemory = true}.Initialize();
_documentStore.DatabaseCommands.DisableAllCaching();
IndexCreation.CreateIndexes(typeof (MyIndex).Assembly, _documentStore);
}
[TearDown]
public void TearDown()
{
_documentStore.Dispose();
}
[Test]
public void ShouldWork()
{
InitData();
IList<MyIndex.MapReduceResult> mapReduceResults = null;
using (var session = _documentStore.OpenSession())
{
mapReduceResults =
session.Query<MyIndex.MapReduceResult>(
MyIndex.INDEX_NAME)
.Customize(x => x.WaitForNonStaleResults()).ToArray();
}
Assert.That(mapReduceResults.Count, Is.EqualTo(1));
}
private void InitData()
{
var visitOne = new Visit
{
Id = "visits/64",
MetaData = new MetaData {CreatedDate = new DateTime(1975, 8, 6, 0, 14, 0)},
MediaSourceId = 1,
};
var visitPageVersionOne = new VisitPageVersion
{
Id = "VisitPageVersions/123",
MetaData = new MetaData {CreatedDate = new DateTime(1975, 8, 6, 0, 14, 0)},
VisitId = "visits/64",
Version = "1"
};
using (var session = _documentStore.OpenSession())
{
session.Store(visitOne);
session.Store(visitPageVersionOne);
session.SaveChanges();
}
}
public class MyIndex :
AbstractMultiMapIndexCreationTask
<MyIndex.MapReduceResult>
{
public const string INDEX_NAME = "MyIndex";
public override string IndexName
{
get { return INDEX_NAME; }
}
public class MapReduceResult
{
public string VisitId { get; set; }
public int? MediaSourceId { get; set; }
public string Version { get; set; }
public int Count { get; set; }
}
public MyIndex()
{
AddMap<Visit>(
visits =>
from visit in visits
select new
{
VisitId = visit.Id,
MediaSourceId = (int?) visit.MediaSourceId,
Version = (string) null,
Count = 1
});
AddMap<VisitPageVersion>(
visitPageVersions =>
from visitPageVersion in visitPageVersions
select new
{
VisitId = visitPageVersion.VisitId,
MediaSourceId = (int?) null,
Version = visitPageVersion.Version,
Count = 0
});
Reduce =
results =>
from result in results
group result by result.VisitId
into g
select
new
{
VisitId = g.Key,
MediaSourceId = (int?) g.Select(x => x.MediaSourceId).FirstOrDefault(),
Version = g.Select(x => x.Version).FirstOrDefault(),
Count = g.Sum(x => x.Count)
};
}
}
}
You don't need to do anything to give nullables special treatment.
RavenDB will already take care of that.