Click here to Skip to main content
14,297,239 members

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

Rate this:
5.00 (13 votes)
Please Sign up or sign in to vote.
5.00 (13 votes)
31 Aug 2016CPOL
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.

Sample Class Diagram

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

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

It will throw following exception:

Circular Reference Exception

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<branch> deserializedBranches = JsonConvert.DeserializeObject<list<branch>>(jBranches, new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });

	return Request.CreateResponse(HttpStatusCode.OK);
}
</list<branch></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
  }, …

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author


Comments and Discussions

 
QuestionGood Pin
Member 1272723112-Dec-16 20:54
memberMember 1272723112-Dec-16 20:54 
AnswerRe: Good Pin
Member 1272723112-Dec-16 20:55
memberMember 1272723112-Dec-16 20:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Article
Posted 30 Aug 2016

Stats

21.5K views
382 downloads
22 bookmarked