Ok, here is a very simple and practical example of the difference…
I have two classes:
[Serializable()]
public class HighScoreEntry
{
public DateTime DateAchieved = DateTime.UtcNow;
public int Level = 0;
public int Difficulty = 0;
public long Score = 0;
public int Rank = 0;
public string Initials = "";
//TODO: Add Game Stats object
public HighScoreEntry()
{
}
}
and then just a simple collection:
public class HighScoreCollection : List<HighScoreEntry>
{
}
So Serialize the Collection in XML I use:
/// <summary>
/// Returns a string of XML that represents the object passed in.
/// </summary>
/// <param name="p_object">The object to serialize.</param>
/// <returns>XML string representing object</returns>
/// <remarks></remarks>
public static string XMLSerialize(object p_object)
{
System.IO.MemoryStream tempMemStream = new System.IO.MemoryStream();
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings();
string tempString;
settings.Indent = true;
settings.Encoding = System.Text.Encoding.UTF8;
settings.NewLineChars = System.Environment.NewLine;
settings.NewLineHandling = System.Xml.NewLineHandling.Replace;
using (System.IO.StringWriter stringWriter = new System.IO.StringWriter())
{
using (System.Xml.XmlWriter xmlWriter = System.Xml.XmlWriter.Create(stringWriter, settings))
{
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(p_object.GetType());
serializer.Serialize(xmlWriter, p_object);
tempString = stringWriter.ToString();
xmlWriter.Close();
}
stringWriter.Close();
}
return tempString;
}
and I get the output of
<?xml version="1.0" encoding="utf-16"?>
<ArrayOfHighScoreEntry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<HighScoreEntry>
<DateAchieved>2020-12-29T03:21:15.4197663Z</DateAchieved>
<Level>0</Level>
<Difficulty>0</Difficulty>
<Score>19800000</Score>
<Rank>1</Rank>
<Initials>AAA</Initials>
</HighScoreEntry>
<HighScoreEntry>
<DateAchieved>2020-12-29T03:21:15.4197663Z</DateAchieved>
<Level>0</Level>
<Difficulty>0</Difficulty>
<Score>18000</Score>
<Rank>2</Rank>
<Initials>BBB</Initials>
</HighScoreEntry>
<HighScoreEntry>
<DateAchieved>2020-12-29T03:21:15.4197663Z</DateAchieved>
<Level>0</Level>
<Difficulty>0</Difficulty>
<Score>17000</Score>
<Rank>3</Rank>
<Initials>CCC</Initials>
</HighScoreEntry>
<HighScoreEntry>
<DateAchieved>2020-12-29T03:21:15.4197663Z</DateAchieved>
<Level>0</Level>
<Difficulty>0</Difficulty>
<Score>16000</Score>
<Rank>4</Rank>
<Initials>DDD</Initials>
</HighScoreEntry>
<HighScoreEntry>
<DateAchieved>2020-12-29T03:21:15.4197663Z</DateAchieved>
<Level>0</Level>
<Difficulty>0</Difficulty>
<Score>15000</Score>
<Rank>5</Rank>
<Initials>EEE</Initials>
</HighScoreEntry>
<HighScoreEntry>
<DateAchieved>2020-12-29T03:21:15.4197663Z</DateAchieved>
<Level>0</Level>
<Difficulty>0</Difficulty>
<Score>14000</Score>
<Rank>6</Rank>
<Initials>FFF</Initials>
</HighScoreEntry>
<HighScoreEntry>
<DateAchieved>2020-12-29T03:21:15.4197663Z</DateAchieved>
<Level>0</Level>
<Difficulty>0</Difficulty>
<Score>13000</Score>
<Rank>7</Rank>
<Initials>GGG</Initials>
</HighScoreEntry>
<HighScoreEntry>
<DateAchieved>2020-12-29T03:21:15.4197663Z</DateAchieved>
<Level>0</Level>
<Difficulty>0</Difficulty>
<Score>12000</Score>
<Rank>8</Rank>
<Initials>HHH</Initials>
</HighScoreEntry>
<HighScoreEntry>
<DateAchieved>2020-12-29T03:21:15.4197663Z</DateAchieved>
<Level>0</Level>
<Difficulty>0</Difficulty>
<Score>11000</Score>
<Rank>9</Rank>
<Initials>III</Initials>
</HighScoreEntry>
<HighScoreEntry>
<DateAchieved>2020-12-29T03:21:15.4197663Z</DateAchieved>
<Level>0</Level>
<Difficulty>0</Difficulty>
<Score>10000</Score>
<Rank>10</Rank>
<Initials>JJJ</Initials>
</HighScoreEntry>
</ArrayOfHighScoreEntry>
The JSON serialization is a little trickier as it uses a Contract Resolver…(at least in my case).
This allows me to include or exclude properties as I need. 99% of the time, I just serialize everything.
public class JsonContractResolver : DefaultContractResolver
{
System.Collections.Generic.List<string> m_excludePropertyNames = null;
System.Collections.Generic.List<string> m_includePropertyNames = null;
public JsonContractResolver(System.Collections.Generic.List<string> p_excludePropertyNames, System.Collections.Generic.List<string> p_includePropertyNames)
{
m_excludePropertyNames = p_excludePropertyNames;
m_includePropertyNames = p_includePropertyNames;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
string propertyName = "";
string className = "";
for (int i = properties.Count - 1; i >= 0; i--)
{
propertyName = properties[i].PropertyName;
switch (propertyName)
{
default:
propertyName = properties[i].PropertyName;
className = type.FullName;
if (m_excludePropertyNames != null)
{
if (m_excludePropertyNames.Contains(propertyName) || m_excludePropertyNames.Contains(String.Format("{0}.{1}", className, propertyName)))
{
properties.RemoveAt(i);
}
}
if (m_includePropertyNames != null)
{
if (!m_includePropertyNames.Contains(propertyName) && !m_includePropertyNames.Contains(String.Format("{0}.{1}", className, propertyName)))
{
properties.RemoveAt(i);
}
}
break;
}
}
return properties;
}
}
Then to serialize the object I use:
/// <summary>
/// This takes an object and serializes it using the Newton Json Library.
/// </summary>
/// <param name="p_gameObject">The object to serialize</param>
/// <returns></returns>
public static string JsonSerialize(object p_gameObject)
{
return JsonSerialize(p_gameObject, null, null);
}
/// <summary>
/// This takes an object and serializes it using the Newton Json Library.
/// </summary>
/// <param name="p_gameObject">The object to serialize</param>
/// <param name="p_excludePropertyNames">A list of property names to exclude from the serialization. You can specify properties on children as well if you fully qualify the property with the full type.</param>
/// <param name="p_includePropertyNames">A list of property names to include from the serialization. You can specify properties on children as well if you fully qualify the property with the full type.</param>
/// <returns></returns>
public static string JsonSerialize(object p_gameObject, System.Collections.Generic.List<string> p_excludePropertyNames, System.Collections.Generic.List<string> p_includePropertyNames)
{
return JsonConvert.SerializeObject(p_gameObject, Formatting.Indented, new JsonSerializerSettings { ContractResolver = new JsonContractResolver(p_excludePropertyNames, p_includePropertyNames) });
}
I then have code that loads and saves my high scores (This is Windows Centric, so you can modify for different platforms as needed)
public static string SaveHighScores()
{
string jsonData = "";
string fileName = "";
try
{
jsonData = JsonSerialize(GlobalInfo.HighScores);
fileName = GetAssetFilename(AssetTypes.Settings, "HighScores.json");
if (fileName == "")
{
return "Invalid HighScores Filename";
}
System.IO.File.WriteAllText(fileName, jsonData);
}
catch (Exception ex)
{
return ex.ToString();
}
return string.Empty;
}
public static string LoadHighScores()
{
string jsonData = "";
string fileName = "";
try
{
fileName = GetAssetFilename(AssetTypes.Settings, "HighScores.json");
if (fileName == "")
{
return "Invalid HighScores Filename";
}
if (System.IO.File.Exists(fileName))
{
jsonData = System.IO.File.ReadAllText(fileName);
GlobalInfo.HighScores = (HighScoreCollection)JsonDeserialize(jsonData, typeof(HighScoreCollection));
}
else
{
GlobalInfo.HighScores = new HighScoreCollection();
GlobalInfo.HighScores.Add(new HighScoreEntry("AAA", 1, 19800000, null));
GlobalInfo.HighScores.Add(new HighScoreEntry("BBB", 2, 18000, null));
GlobalInfo.HighScores.Add(new HighScoreEntry("CCC", 3, 17000, null));
GlobalInfo.HighScores.Add(new HighScoreEntry("DDD", 4, 16000, null));
GlobalInfo.HighScores.Add(new HighScoreEntry("EEE", 5, 15000, null));
GlobalInfo.HighScores.Add(new HighScoreEntry("FFF", 6, 14000, null));
GlobalInfo.HighScores.Add(new HighScoreEntry("GGG", 7, 13000, null));
GlobalInfo.HighScores.Add(new HighScoreEntry("HHH", 8, 12000, null));
GlobalInfo.HighScores.Add(new HighScoreEntry("III", 9, 11000, null));
GlobalInfo.HighScores.Add(new HighScoreEntry("JJJ", 10, 10000, null));
SaveHighScores();
}
}
catch (Exception ex)
{
return ex.ToString();
}
return string.Empty;
}
The GetAssetFilename is just a method I have that returns the path to where I load/save settings, etc…
So when I serialize the collection in JSON I get
[
{
"DateAchieved": "2020-12-29T03:21:15.4197663Z",
"Level": 0,
"Difficulty": 0,
"PlayerStats": null,
"Score": 19800000,
"Rank": 1,
"Initials": "AAA"
},
{
"DateAchieved": "2020-12-29T03:21:15.4197663Z",
"Level": 0,
"Difficulty": 0,
"PlayerStats": null,
"Score": 18000,
"Rank": 2,
"Initials": "BBB"
},
{
"DateAchieved": "2020-12-29T03:21:15.4197663Z",
"Level": 0,
"Difficulty": 0,
"PlayerStats": null,
"Score": 17000,
"Rank": 3,
"Initials": "CCC"
},
{
"DateAchieved": "2020-12-29T03:21:15.4197663Z",
"Level": 0,
"Difficulty": 0,
"PlayerStats": null,
"Score": 16000,
"Rank": 4,
"Initials": "DDD"
},
{
"DateAchieved": "2020-12-29T03:21:15.4197663Z",
"Level": 0,
"Difficulty": 0,
"PlayerStats": null,
"Score": 15000,
"Rank": 5,
"Initials": "EEE"
},
{
"DateAchieved": "2020-12-29T03:21:15.4197663Z",
"Level": 0,
"Difficulty": 0,
"PlayerStats": null,
"Score": 14000,
"Rank": 6,
"Initials": "FFF"
},
{
"DateAchieved": "2020-12-29T03:21:15.4197663Z",
"Level": 0,
"Difficulty": 0,
"PlayerStats": null,
"Score": 13000,
"Rank": 7,
"Initials": "GGG"
},
{
"DateAchieved": "2020-12-29T03:21:15.4197663Z",
"Level": 0,
"Difficulty": 0,
"PlayerStats": null,
"Score": 12000,
"Rank": 8,
"Initials": "HHH"
},
{
"DateAchieved": "2020-12-29T03:21:15.4197663Z",
"Level": 0,
"Difficulty": 0,
"PlayerStats": null,
"Score": 11000,
"Rank": 9,
"Initials": "III"
},
{
"DateAchieved": "2020-12-29T03:21:15.4197663Z",
"Level": 0,
"Difficulty": 0,
"PlayerStats": null,
"Score": 10000,
"Rank": 10,
"Initials": "JJJ"
}
]
The XML file is 2,533 bytes, the JSON is 1,897 bytes. The JSON file is 26% smaller in this case. When the files get very large then the savings really make a difference.