Title: Use JSON to serialize and deserialize objects in C#
JSON (JavaScript Object Notation) is a standard for textual storage and interchange of information, much as XML is. Before you roll your eyes and ask if we really need another language to do what XML does, consider how verbose XML is. Even relatively simple object hierarchies can take up a considerable amount of space when represented by XML. JSON is a more compact format that stores the same kinds of information in less space.
When XML first came out, I thought to myself, "This is a really verbose language. It could be so much more concise. I guess people are willing to spend the extra space to get a more readable format. And after all, storage space is cheaper and network speed is faster than ever before." If you remember having similar thoughts, *now* you can roll your eyes.
JSON's basic data types are:
- Number
- String
- Boolean
- Array (sequence of values separated by commas and enclosed in brackets [ ])
- Object (a collection of key:value pairs with pairs separated by commas and the whole collection surrounded by braces { })
- null
For example, the following text shows a JSON representation of a Customer object.
{
"City":"Bugsville",
"EmailAddresses":
[
"somewhere@someplace.com",
"nowhere@noplace.com",
"root@everywhere.org"
],
"Name":"Rod Stephens",
"Orders":
[
{"Description":"Pencils, dozen","Price":1.13,"Quantity":10},
{"Description":"Notepad","Price":0.99,"Quantity":3},
{"Description":"Cookies","Price":3.75,"Quantity":1}
],
"PhoneNumbers":
[
{"Key":"Home","Value":"111-222-3333"},
{"Key":"Cell","Value":"222-333-4444"},
{"Key":"Work","Value":"333-444-5555"}
],
"State":"CA",
"Street":"1337 Leet St",
"Zip":"98765"
}
Some of the fields such as City, Name, and State are simple string values. The EmailAddresses value is an array of strings.
The Orders value is an array of Order objects, each of which has a Description, Price, and Quantity. (Really this should be a collection of Order objects, each of which has a collection of OrderItem objects that have Description, Price, and Quantity properties, but the structure shown here is complicated enough for this example already.)
The PhoneNumbers value is a dictionary where the keys are phone number types (Home, Cell, Work) and the values are the phone number strings.
This example builds a Customer object. It then serializes it into a JSON format and then deserializes it to re-create the original object.
The following code shows the Order class.
// Add a reference to System.Runtime.Serialization.
using System.Runtime.Serialization;
namespace howto_use_json
{
[Serializable]
public class Order
{
[DataMember]
public string Description;
[DataMember]
public int Quantity;
[DataMember]
public decimal Price;
// Return a textual representation of the order.
public override string ToString()
{
decimal total = Quantity * Price;
return Description + ": " +
Quantity.ToString() + " @ " +
Price.ToString("C") + " = " +
total.ToString("C");
}
}
}
To allow the program to serialize Order objects, the class must be marked with the Serializable attribute. That attribute is defined in the System.Runtime.Serialization namespace, so the code includes a using directive to make using that namespace easier. To use the namespace, you also need to add a reference to System.Runtime.Serialization at design time.
The fields within the class are marked with the DataMember attribute so the serializer knows to serialize them.
The last part of the class is a ToString method that simply returns a textual representation of the object's values.
The following code shows the Customer class.
// Add a reference to System.Runtime.Serialization.
using System.Runtime.Serialization;
// Add a reference to System.ServiceModel.Web.
using System.Runtime.Serialization.Json;
using System.IO;
namespace howto_use_json
{
[Serializable]
public class Customer
{
[DataMember]
public string Name = "";
[DataMember]
public string Street = "";
[DataMember]
public string City = "";
[DataMember]
public string State = "";
[DataMember]
public string Zip = "";
[DataMember]
public Dictionary<string, string> PhoneNumbers =
new Dictionary<string, string>();
[DataMember]
public List<string> EmailAddresses =
new List<string>();
[DataMember]
public Order[] Orders = null;
... (Other code shown later) ...
}
}
This class starts much as the Order class does. It also uses the System.Runtime.Serialization namespace so it includes the corresponding using directive.
The class also uses the System.Runtime.Serialization.Json namespace (which requires you to add a reference to System.ServiceModel.Web) and the System.IO namespaces, so it also includes using directives for them.
The class starts by defining some simple properties such as Name, Street, and City.
The class defines the PhoneNumbers member as a Dictionary<string, string>. It defines the EmailAddresses member as List<string>. The serializer automatically converts these into the appropriate JSON types.
To make serializing and deserializing objects easier, the class includes the methods ToJson and FromJson. The ToJson method shown in the following code returns a JSON representation of an object.
// Return the JSON serialization of the object.
public string ToJson()
{
// Make a stream to serialize into.
using (MemoryStream stream = new System.IO.MemoryStream())
{
// Serialize into the stream.
DataContractJsonSerializer serializer
= new DataContractJsonSerializer(typeof(Customer));
serializer.WriteObject(stream, this);
stream.Flush();
// Get the result as text.
stream.Seek(0, SeekOrigin.Begin);
using (StreamReader reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}
This method creates a MemoryStream in which to write. It then creates a DataContractJsonSerializer object to serialize the Customer object. It calls the serializer's WriteObject method to write the current Customer object into the stream and flushes the stream.
Next, the code rewinds the stream to the beginning, creates an associated StreamReader, and uses the reader's ReadToEnd method to get the serialization out of the stream and return it. (This seems needlessly awkward to me. If you find a simpler method, please post it in the comments below.)
The static FromJson method shown in the following code takes a JSON serialization of a Customer object, uses it to re-create the object, and returns the new object.
// Create a new Customer from a JSON serialization.
public static Customer FromJson(string json)
{
// Make a stream to read from.
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(json);
writer.Flush();
stream.Position = 0;
// Deserialize from the stream.
DataContractJsonSerializer serializer
= new DataContractJsonSerializer(typeof(Customer));
Customer cust = (Customer)serializer.ReadObject(stream);
// Return the result.
return cust;
}
This method creates a MemoryStream and an associated StreamWriter. It uses the writer to write the JSON serialization into the stream. It then flushes the writer and rewinds the stream.
Next, the code creates a DataContractJsonSerializer object to deserialize the Customer object. It calls the serializer's ReadObject to read the object's serialization from the stream, casts the result into a Customer object, and returns the object.
The ToString method shown in the following code is the last piece of the Customer class. It simply returns a textual representation of the Customer object.
// Return a newline separated text representation.
public override string ToString()
{
// Display the basic information.
string result = "Customer" + Environment.NewLine;
result += " " + Name + Environment.NewLine;
result += " " + Street + Environment.NewLine;
result += " " + City + " " + State + " " +
Zip + Environment.NewLine;
// Display phone numbers.
result += " Phone Numbers:" + Environment.NewLine;
foreach (KeyValuePair pair in PhoneNumbers)
{
result += " " + pair.Key + ": " +
pair.Value + Environment.NewLine;
}
// Display email addresses.
result += " Email Addresses:" + Environment.NewLine;
foreach (string address in EmailAddresses)
{
result += " " + address + Environment.NewLine;
}
// Display orders.
result += " Orders:" + Environment.NewLine;
foreach (Order order in Orders)
{
result += " " + order.ToString() +
Environment.NewLine;
}
return result;
}
The ToString method is a bit long but reasonably straightforward.
The following code shows how the main program demonstrates JSON serialization by serializing and deserializing a Customer object.
private void Form1_Load(object sender, EventArgs e)
{
// Make an object to serialize.
Customer cust = new Customer()
{
Name = "Rod Stephens",
Street = "1337 Leet St",
City = "Bugsville",
State = "CA",
Zip = "98765",
PhoneNumbers = new Dictionary<string, string>()
{
{"Home", "111-222-3333"},
{"Cell", "222-333-4444"},
{"Work", "333-444-5555"},
},
EmailAddresses = new List<string>()
{
"somewhere@someplace.com",
"nowhere@noplace.com",
"root@everywhere.org",
},
};
cust.Orders = new Order[3];
cust.Orders[0] = new Order()
{
Description = "Pencils, dozen",
Quantity = 10,
Price = 1.13m
};
cust.Orders[1] = new Order()
{
Description = "Notepad",
Quantity = 3,
Price = 0.99m
};
cust.Orders[2] = new Order()
{
Description = "Cookies",
Quantity = 1,
Price = 3.75m
};
// Display the serialization.
string serialization = cust.ToJson();
txtJson.Text = serialization;
txtJson.Select(0, 0);
// Deserialize.
Customer new_cust = Customer.FromJson(serialization);
txtProperties.Text = new_cust.ToString();
txtProperties.Select(0, 0);
}
The form's Load event handler starts by initializing a Customer object complete with phone numbers, email addresses, and Order objects.
Next, the code calls the Customer object's ToJson method to get its serialization and display it in the txtJson TextBox. (Notice that the JSON serialization in the picture doesn't include nice line breaks and formatting to align the data nicely. It sacrifices that to save space.)
The code then calls the Customer class's FromJson method to deserialize the serialization and create a new Customer object. It finishes by using the new object's ToString method to show its properties in the txtProperties TextBox.
This example may seem complicated, but it's actually not too bad. It's a bit longer than it might otherwise be because the Customer and Order classes include several useful data types such as a list, dictionary, and array of objects. The actual JSON code in the ToJson and FromJson methods is somewhat confusing, but it's fairly short. Those methods also make using JSON to serialize and deserialize objects easy in practice.
Download the example to experiment with it and to see additional details.
|