最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Using JavaScript to deserialize references in a complex object graph from SignalRJson.NET - Stack Overflow

programmeradmin3浏览0评论

I'm using SignalR to return a plex object graph to my JavaScript client. This object graph has multiple references throughout to the same object, so the JSON that SignalR/Json.NET returns looks a lot like this:

{
    "$id": "57",
    "Name": "_default",
    "User": {
        "$id": "58",
        "UserTag": "ken",
        "Sessions": [{
            "$id": "59",
            "SessionId": "0ca7474e-273c-4eb2-a0c1-1eba2f1a711c",
            "User": {
                "$ref": "58"
            },
            "Room": {
                "$ref": "57"
            }
        }],
    },

    "Sessions": [{
        "$ref": "59"
    }]
}

(Of course, a lot more plicated in real life, but you get the idea.)

And of course, when Json.NET is serializing by reference rather than by value, it assigns each object a $id value (e.g., "$id":"57", and then later just refers to that object using that id (e.g., "$ref":"57". 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.

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?

I could presumably write my own general-purpose deserializer, but I have to imagine that somebody else has already tackled this problem, and I'd just as soon not reinvent any wheels. Unfortunately, my Google skills apparently aren't sufficient to locate that solution :-).

Edit:

I see that there's an IETF draft proposal about how this sort of thing is supposed to work. And it looks like the always helpful Douglas Crockford has a tentative implementation of it. Unfortunately, the IETF proposal uses a different schema than Json.NET uses.

I'm using SignalR to return a plex object graph to my JavaScript client. This object graph has multiple references throughout to the same object, so the JSON that SignalR/Json.NET returns looks a lot like this:

{
    "$id": "57",
    "Name": "_default",
    "User": {
        "$id": "58",
        "UserTag": "ken",
        "Sessions": [{
            "$id": "59",
            "SessionId": "0ca7474e-273c-4eb2-a0c1-1eba2f1a711c",
            "User": {
                "$ref": "58"
            },
            "Room": {
                "$ref": "57"
            }
        }],
    },

    "Sessions": [{
        "$ref": "59"
    }]
}

(Of course, a lot more plicated in real life, but you get the idea.)

And of course, when Json.NET is serializing by reference rather than by value, it assigns each object a $id value (e.g., "$id":"57", and then later just refers to that object using that id (e.g., "$ref":"57". 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.

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?

I could presumably write my own general-purpose deserializer, but I have to imagine that somebody else has already tackled this problem, and I'd just as soon not reinvent any wheels. Unfortunately, my Google skills apparently aren't sufficient to locate that solution :-).

Edit:

I see that there's an IETF draft proposal about how this sort of thing is supposed to work. And it looks like the always helpful Douglas Crockford has a tentative implementation of it. Unfortunately, the IETF proposal uses a different schema than Json.NET uses.

Share Improve this question edited Oct 7, 2021 at 7:13 CommunityBot 11 silver badge asked Dec 8, 2012 at 21:21 Ken SmithKen Smith 20.4k17 gold badges104 silver badges151 bronze badges 4
  • 1 I am curious how would you de-serialize the object in your example, where it refers to itself ($id=57)? – akonsu Commented Dec 8, 2012 at 21:56
  • That's part of the magic of a good deserializer :-). But yeah, it's figuring out those bits that I'd rather not do myself if somebody else has done all the hard work. – Ken Smith Commented Dec 8, 2012 at 22:05
  • 2 my point is that if a deserializer replaces the references with actual objects then in your example you will get an object of infinite depth. – akonsu Commented Dec 8, 2012 at 22:12
  • 3 It's only infinite if you insist on traversing it :-). In memory, it's just pointer A pointing at pointer B, which in turn points back to pointer A. If you're working with something like Microsoft's Entity Framework, you get stuff like that all the time, and it's not a problem. You just need to have a deserializer that knows how to deal with it. – Ken Smith Commented Dec 8, 2012 at 22:46
Add a ment  | 

1 Answer 1

Reset to default 18

Well, I think this will do it. I modified Crockford's cycle.js to handle the reference format that Json.NET uses. And because TypeScript is an unspeakably better language than JavaScript, I rewrote it in TS. I certainly don't swear that it has no bugs (if anybody points them out, I'll try to fix 'em), but it seems to handle the plex object graphs I've throw at it so far.

export function retrocycle(obj: any): void {
    var catalog: any[] = [];
    catalogObject(obj, catalog);
    resolveReferences(obj, catalog);
}

function catalogObject(obj, catalog: any[]):void {

    // 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: number;
    if (obj && typeof obj === 'object') {
        var id:string = 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) {
                catalogObject(obj[i], catalog);
            }
        } else {
            for (name in obj) {
                if (typeof obj[name] === 'object') {
                    catalogObject(obj[name], catalog);
                }
            }
        }
    }
}

function resolveReferences(obj: any, catalog: any[]) {

    // The resolveReferences function walks recursively through the object looking for $ref
    // properties. When it finds one that has a value that is an id, then it
    // replaces the $ref object with a reference to the object that is found in the catalog under
    // that id.

    var i:number, item:any, name:string, id:string;

    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 {
                        resolveReferences(item, catalog);
                    }
                }
            }
        } else {
            for (name in obj) {
                if (typeof obj[name] === 'object') {
                    item = obj[name];
                    if (item) {
                        id = item.$ref;
                        if (typeof id === 'string') {
                            obj[name] = catalog[id];
                        } else {
                            resolveReferences(item, catalog);
                        }
                    }
                }
            }
        }
    }
}

And the equivalent JS:

function retrocycle(obj) {
    var catalog = [];
    catalogObject(obj, catalog);
    resolveReferences(obj, catalog);
}

function catalogObject(obj, catalog) {
    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) {
                catalogObject(obj[i], catalog);
            }
        } else {
            for (name in obj) {
                if (typeof obj[name] === 'object') {
                    catalogObject(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 {
                        resolveReferences(item, catalog);
                    }
                }
            }
        } else {
            for (name in obj) {
                if (typeof obj[name] === 'object') {
                    item = obj[name];
                    if (item) {
                        id = item.$ref;
                        if (typeof id === 'string') {
                            obj[name] = catalog[id];
                        } else {
                            resolveReferences(item, catalog);
                        }
                    }
                }
            }
        }
    }
}

You use it kinda like so (assuming you've got SignalR hubs wired up):

$.connection.roomHub.server.joinRoom()
    .done(function(room) {
        retrocycle(room);
    });

I also created a quick-and-dirty little repository out on BitBucket for it: https://bitbucket/smithkl42/jsonnetdecycle.

发布评论

评论列表(0)

  1. 暂无评论