Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Serialize/Deserialize objects by reference to transfer json between server and client

0.00/5 (No votes)
31 Aug 2016 1  
This article will find out a solution to serialize/deserialize object by reference at server and client, also will serialize/deserialize objects having circular references.

Introduction

There are some common challenges while transferring data between server and client in JSON format. I’m trying here to address few of them:

  • Serialize/Deserialize object by reference or preserve association between objects
  • Serialize/Deserialize circular references

Serializers like DataContractJsonSerializer and JavaScriptSerializer are serializing objects by value. Result is loss of object references or association between objects. That means,if an object is referenced by other objects, that object will be copied locally to all those objects. So, it's a data duplicacy. Another challenge is these serializers do not support circular references and throw exception.

Ideally, if instance of an object is referenced at multiple places in object graph, only single instance should be created during deserialization. And that created instance should be referenced from everywhere else. Important thing is that solution should work for server and client both. So that data transfer from server to client and client to server should retain associations between objects after deserialize.

Use case

Let’s begin with following sample class diagram. Here Branch and Customer classes are associated with multiple classes. Also there is a circular reference in Account class.

<img alt="Sample Class Diagram" src="http://www.codeproject.com/KB/aspnet/1121469/class_diagram.png" style="width: 627px; height: 288px;" />

Generate classes based on above diagram. Try to serialize branch object into JSON format:

IEnumerable
 branches = Branch.GetAllBranches();
var js = new JavaScriptSerializer();
string json = js.Serialize(branches);
</branch>

It will throw following exception:

<img alt="Circular Reference Exception" src="http://www.codeproject.com/KB/aspnet/1121469/circular_reference_error.png" style="width: 452px; height: 68px;" />

Even if there are no circular references, it will serialize object by value. Resulting loss of object references and data duplicity. In this case, duplicate Customer and Branch data will be generated inside Account and Loan.

Server side solution (C#, Web API):

So, let’s move to fix these issues with Json.NET. Json.NET offers many features not found in the JavaScriptSerializer and DataContractSerializer. Like serializing objects by reference, circular references, LINQ to JSON, ISO8601 dates, non-default constructors, interface based deserialization and many more

First, you need to install Json.NET, use following NuGet command to install Json.NET:

Install-Package Newtonsoft.Json

I got Newtonsoft.Json 9.0.1 but you may have other version. Next, add Json.NET namespace in your code:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

All setup done, it’s time to implement the solution. Make changes in our previous solution and replace JavaScriptSerializer with this new one.

// GET api/GetAllBranches
public HttpResponseMessage GetAllBranches()
{
	var branches = Branch.GetAllBranches();
	string json = JsonConvert.SerializeObject(branches, Formatting.Indented, new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });
	return Request.CreateResponse(HttpStatusCode.OK, json);
}

Here Json.NET serializes object by reference rather than by value. It assigns each object a "$id" value e.g., {"$id":"1"}, and then later just refers to that object using that id e.g., {"$ref":"1"}. And so far as I can tell, when it is Json.NET (using C#/.NET) that is deserializing those references, it places the appropriate instances of the object in the appropriate places. It solves both issues; preserve association between objects and circular reference. Use below code block to deserialize json data transferred from client code:

// POST api/PostBranches
public HttpResponseMessage PostBranches(JObject jsonData)
{
	dynamic m = jsonData;
	string jBranches = m.branches.Value as string;

	List
 deserializedBranches = JsonConvert.DeserializeObject<list
>(jBranches, new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });

	return Request.CreateResponse(HttpStatusCode.OK);
}
</list
</branch>

Client side solution (javascript):

All good so far - but what's the best way to deserialize these in JavaScript, so that I actually get the appropriate object instances in the appropriate places, instead of just weird $ref fields? And also, incorporate $ref in json for each references in javascript objects, so that I can retain object references at server side.

I found a javascript library JsonNetDecycle.js from github. Made few changes into that. Idea is to replace "$ref" with reference object while deserializing. During serialize, add "$id" for each unique object and replace object reference with "$ref" and corresponding object id.

Code to deserializing JSON data is:

var jsObject = retrocycle(JSON.parse(jsonData));
function retrocycle(obj) {
        var catalog = [];
        findReferences(obj, catalog);
        return resolveReferences(obj, catalog);
}
function findReferences(obj, catalog) {
    // The catalogObject function walks recursively through an object graph
    // looking for $id properties. When it finds an object with that property, then
    // it adds it to the catalog under that key.
    var i;
    if (obj && typeof obj === "object") {
        var id = obj.$id;
        if (typeof id === "string") {
            catalog[id] = obj;
        }
        if (Object.prototype.toString.apply(obj) === "[object Array]") {
            for (i = 0; i < obj.length; i += 1) {
                findReferences(obj[i], catalog);
            }
        }
        else {
            for (name in obj) {
                if (obj.hasOwnProperty(name)) {
                    if (typeof obj[name] === "object") {
                        findReferences(obj[name], catalog);
                    }
                }
            }
        }
    }
}

function resolveReferences(obj, catalog) {
    var i, item, name, id;
    if (obj && typeof obj === "object") {
        if (Object.prototype.toString.apply(obj) === "[object Array]") {
            for (i = 0; i < obj.length; i += 1) {
                item = obj[i];
                if (item && typeof item === "object") {
                    id = item.$ref;
                    if (typeof id === "string") {
                        obj[i] = catalog[id];
                    }
                    else {
                        obj[i] = resolveReferences(item, catalog);
                    }
                }
            }
        }
        else if (obj.$values && Object.prototype.toString.apply(obj.$values) === "[object Array]") {
            var arr = new Array();
            for (i = 0; i < obj.$values.length; i += 1) {
                item = obj.$values[i];
                if (item && typeof item === "object") {
                    id = item.$ref;
                    if (typeof id === "string") {
                        arr[i] = catalog[id];
                    }
                    else {
                        arr[i] = resolveReferences(item, catalog);
                    }
                }
                else {
                    arr[i] = item;
                }
            }
            obj = arr;
        }
        else {
            for (name in obj) {
                if (obj.hasOwnProperty(name)) {
                    if (typeof obj[name] === "object") {
                        item = obj[name];
                        if (item) {
                            id = item.$ref;
                            if (typeof id === "string") {
                                obj[name] = catalog[id];
                            }
                            else {
                                obj[name] = resolveReferences(item, catalog);
                            }
                        }
                    }
                }
            }
        }
    }
    //removeAutoGenProperty(catalog);
    return obj;
}

function removeAutoGenProperty(catalog) {
    for (i = 0; i < catalog.length; i += 1) {
        var obj = catalog[i];
        if (obj && typeof obj === "object") {
            var id = obj['$id'];
            if (typeof id != "undefined") {
                delete obj['$id'];
            }
        }
    }
}

Call following function to Serialize javascript object to JSON, which will be further used by Web API to Deserialize. Function decycle takes object as input parameter and returns JSON.

function decycle(obj) {
    var catalog = []; // Keep a reference to each unique object or array
    var newObj = getDecycledCopy(obj, catalog);
    return newObj;
}

function getDecycledCopy(obj, catalog) {
    // The createReferences function recurses through the object, producing the deep copy.
    var i; // The loop counter
    var name; // Property name
    var nu; // The new object or array
    switch (typeof obj) {
        case "object":
            // typeof null === 'object', so get out if this value is not really an object.
            // Also get out if it is a weird builtin object.
            if (obj === null || obj instanceof Boolean || obj instanceof Date || obj instanceof Number || obj instanceof RegExp || obj instanceof String) {
                return obj;
            }
            for (i = 0; i < catalog.length; i += 1) {
                if (catalog[i] === obj) {
                    return { $ref: i.toString() };
                }
            }
            // Otherwise, accumulate the unique value and its id.
            obj.$id = catalog.length.toString();
            catalog.push(obj);
            // If it is an array, replicate the array.
            if (Object.prototype.toString.apply(obj) === "[object Array]") {
                nu = [];
                for (i = 0; i < obj.length; i += 1) {
                    nu[i] = getDecycledCopy(obj[i], catalog);
                }
            }
            else {
                // If it is an object, replicate the object.
                nu = {};
                for (name in obj) {
                    if (Object.prototype.hasOwnProperty.call(obj, name)) {
                        nu[name] = getDecycledCopy(obj[name], catalog);
                    }
                }
            }
            return nu;
        case "number":
        case "string":
        case "boolean":
        default:
            return obj;
    }
}

Output JSON data after serialization will looks like:

{
    "$id": "1",
    "AccountNumber": "IN-1700774952",
    "Balance": 0.0,
    "OpeningDate": "2016-08-23T14:39:55.8165458+05:30",
    "Branch": {
      "$id": "2",
      "Code": "IN-Delhi-1700774952",
      "HeadOffice": {
        "$ref": "2"
      },
      "Bank": {
        "$id": "3",
        "Name": "State Bank Of India",
        "Code": "SBI"
      },
      "City": "Delhi"
    },
    "Customer": null
  }, &hellip;

Final Thoughts:

So here you have it, a way to serialize and deserialize objects by reference through Json.NET and make it compatible with client using javascript library JsonNetDecycle.js at client end. It keeps references intact. Also will prevent redundant data and circular reference issue.

I'll be more than happy for your feedback.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here