13

我正在使用 SignalR 向我的 JavaScript 客户端返回一个复杂的对象图。此对象图对同一对象有多个引用,因此 SignalR/Json.NET 返回的 JSON 看起来很像这样:

{
    "$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"
    }]
}

(当然,在现实生活中要复杂得多,但你明白了。)

当然,当 Json.NET 通过引用而不是按值进行序列化时,它会为每个对象分配一个 $id 值(例如,"$id":"57",然后稍后仅使用该 id 引用该对象(例如,"$ref":"57"。告诉我们,当反序列化这些引用的是 Json.NET(使用 C#/.NET)时,它会将对象的适当实例放置在适当的位置。

到目前为止一切都很好 - 但是在 JavaScript中反序列化这些的最佳方法是什么,以便我实际上在适当的位置获得适当的对象实例,而不仅仅是奇怪的 $ref 字段?

我大概可以编写自己的通用反序列化器,但我必须想象其他人已经解决了这个问题,而且我不会重新发明任何轮子。不幸的是,我的谷歌技能显然不足以找到该解决方案:-)。

编辑:

我看到有一个关于这种事情应该如何工作的IETF 提案草案。看起来总是乐于助人的 Douglas Crockford 已经初步实现了它。不幸的是,IETF 提案使用了与 Json.NET 不同的架构。

4

1 回答 1

16

好吧,我认为这会做到。我修改了 Crockford 的cycle.js来处理 Json.NET 使用的参考格式。而且因为 TypeScript 是一种比 JavaScript 更好的语言,所以我用 TS 重写了它。我当然不发誓它没有错误(如果有人指出它们,我会尝试修复它们),但它似乎可以处理我迄今为止抛出的复杂对象图。

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);
                        }
                    }
                }
            }
        }
    }
}

和等效的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);
                        }
                    }
                }
            }
        }
    }
}

你使用它有点像这样(假设你已经连接了 SignalR 集线器):

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

我还在 BitBucket 上为它创建了一个又快又脏的小存储库:https://bitbucket.org/smithkl42/jsonnetdecycle

于 2012-12-08T23:54:02.540 回答