WebAPI problem when trying to deserialize object with property of the same type (circular reference)

1

I'm doing some tests with WebAPI , to see how it behaves with circular reference, and I found a curious case.

Model

[DataContract(IsReference = true)]
public class Pessoa
{
    [DataMember(EmitDefaultValue = false, Order = 1)]
    public string Nome { get; set; }

    [DataMember(EmitDefaultValue = false, Order = 2)]
    public Pessoa Patner { get; set; }

    [DataMember(EmitDefaultValue = false, Order = 3)]
    public Pessoa Father { get; set; }

    [DataMember(EmitDefaultValue = false, Order = 4)]
    public Pessoa Mother { get; set; }

    [DataMember(EmitDefaultValue = false, Order = 5)]
    public Pessoa[] Children { get; set; }

    [DataMember(EmitDefaultValue = false, Order = 6)]
    public Pessoa[] Siblings { get; set; }

    public Pessoa(string nome)
    {
        this.Nome = nome;
    }
}

ApiController

public async Task<Pessoa[]> ListarFamilia()
{
    var father = new Pessoa("Person 01");
    var mother = new Pessoa("Person 02");
    var child1 = new Pessoa("Person 03");
    var child2 = new Pessoa("Person 04");
    var child3 = new Pessoa("Person 05");

    father.Patner = mother;
    father.Children = new Pessoa[] { child1, child2, child3 };

    mother.Patner = father;
    mother.Children = new Pessoa[] { child1, child2, child3 };

    child1.Father = father;
    child1.Mother = mother;
    child1.Siblings = new Pessoa[] { child2, child3 };

    child2.Father = father;
    child2.Mother = mother;
    child2.Siblings = new Pessoa[] { child1, child3 };

    child3.Father = father;
    child3.Mother = mother;
    child3.Siblings = new Pessoa[] { child1, child2 };

    var familia = new Pessoa[] { father, mother, child1, child2, child3 };
    return familia;
}

public async Task<int> ContarPessoas(Pessoa[] familia)
{
    return familia.Length;
}

Global.asax

GlobalConfiguration.Configure(WebApiConfig.Register);
Newtonsoft.Json.JsonConvert.DefaultSettings = () =>
{
    return new Newtonsoft.Json.JsonSerializerSettings
    {
        ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize,
        PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects
    };
};

When I call the method in /api/Pessoa/ListarFamilia , it returns me the following JSON

[
    {
        "Nome": "Person 01",
        "Patner": {
            "Nome": "Person 02",
            "Patner": {
                "$ref": 1
            },
            "Children": [
                {
                    "Nome": "Person 03",
                    "Father": {
                        "$ref": 1
                    },
                    "Mother": {
                        "$ref": 2
                    },
                    "Siblings": [
                        {
                            "Nome": "Person 04",
                            "Father": {
                                "$ref": 1
                            },
                            "Mother": {
                                "$ref": 2
                            },
                            "Siblings": [
                                {
                                    "$ref": 3
                                },
                                {
                                    "Nome": "Person 05",
                                    "Father": {
                                        "$ref": 1
                                    },
                                    "Mother": {
                                        "$ref": 2
                                    },
                                    "Siblings": [
                                        {
                                            "$ref": 3
                                        },
                                        {
                                            "$ref": 4
                                        }
                                    ],
                                    "$id": 5
                                }
                            ],
                            "$id": 4
                        },
                        {
                            "$ref": 5
                        }
                    ],
                    "$id": 3
                },
                {
                    "$ref": 4
                },
                {
                    "$ref": 5
                }
            ],
            "$id": 2
        },
        "Children": [
            {
                "$ref": 3
            },
            {
                "$ref": 4
            },
            {
                "$ref": 5
            }
        ],
        "$id": 1
    },
    {
        "$ref": 2
    },
    {
        "$ref": 3
    },
    {
        "$ref": 4
    },
    {
        "$ref": 5
    }
]

So far so good, but when I try to send this same JSON to /api/Pessoa/ContarPessoas , the family object is not completely deserialized, since Formatter can not handle the "$ ref".

Finally, it overrides the ContarPessoas method

public async Task<int> ContarPessoas(object request)
{
    var jsonStr = request.ToString();
    var familia = JsonConvert.DeserializeObject<Pessoa[]>(jsonStr);
    return familia.Length;
}

The request arrived correctly, with all { '$ref': 'n' } in their respective places, but DeserializeObject does not recognize them.

Finally, this is the script I'm using on my page html :

<script type="text/javascript">
    JSON.decircle = function (obj) {
        JSON._decircle(obj, []);
    }

    JSON.recircle = function (obj) {
        var refs = [];
        var objs = [];
        JSON._recircleScanIds(obj, refs);
        JSON._recircleScanRefs(obj, objs);

        for (var indice in objs) {
            var obj = objs[indice].obj;
            var key = objs[indice].key;
            var end = obj[key]["$ref"];
            obj[key] = refs[end]
        }
    }

    JSON._decircleCheck = function (obj, key, refs) {
        var child = obj[key];
        if (child && typeof child === "object") {
            if (Array.isArray(child)) {
                for (var indice in child) {
                    JSON._decircleCheck(child, indice, refs)
                }
            } else {
                if (refs.indexOf(child) == -1) {
                    JSON._decircle(child, refs);
                } else {
                    obj[key] = { "$ref": child["$id"] };
                }
            }
        }
    }

    JSON._decircle = function (obj, refs) {
        if (obj && typeof obj === "object") {
            if (Array.isArray(obj)) {
                for (var key in obj) {
                    JSON._decircleCheck(obj, key, refs);
                }
            } else {
                if (refs.indexOf(obj) == -1) {
                    refs.push(obj);
                    obj["$id"] = "" + refs.length;
                    for (var key in obj) {
                        JSON._decircleCheck(obj, key, refs);
                    }
                }
            }
        }
    }

    JSON._recircleScanIds = function (obj, refs) {
        if (obj && typeof obj === "object") {
            if (Array.isArray(obj)) {
                for (var key in obj) {
                    JSON._recircleScanIds(obj[key], refs);
                }
            } else {
                var id = obj["$id"];
                if (id) {
                    refs[id] = obj;
                    delete obj["$id"];
                }

                for (var key in obj) {
                    JSON._recircleScanIds(obj[key], refs);
                }
            }
        }
    }

    JSON._recircleScanRefs = function (obj, objs) {
        if (obj && typeof obj === "object") {
            for (var key in obj) {
                var child = obj[key];
                var isArray = Array.isArray(child);
                if (child && typeof child === "object") {
                    if (!isArray && child["$ref"]) {
                        objs.push({ obj: obj, key: key });
                    } else {
                        JSON._recircleScanRefs(child, objs);
                    }
                }
            }
        }
    }

    var getFamily = function (callback) {
        var httpRequest = new XMLHttpRequest();
        httpRequest.open("POST", "http://localhost:54454/api/Pessoa/ListarFamilia", true);
        httpRequest.responseType = "json";
        httpRequest.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
        httpRequest.addEventListener("readystatechange", function (event) {
            if (httpRequest.readyState == 4) {
                var familia = httpRequest.response;
                JSON.recircle(familia);
                callback(familia);
            }
        })
        httpRequest.send();
    }

    var sendFamily = function (familia, callback) {
        var httpRequest = new XMLHttpRequest();
        httpRequest.open("POST", "http://localhost:54454/api/Pessoa/ContarPessoas", true);
        httpRequest.responseType = "json";
        httpRequest.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
        httpRequest.addEventListener("readystatechange", function (event) {
            if (httpRequest.readyState == 4) {
                var quantidade = httpRequest.response;
                callback(quantidade);
            }
        });

        JSON.decircle(familia);
        httpRequest.send(JSON.stringify(familia));
    }

    getFamily(function (familia) {
        sendFamily(familia, function (quantidade) {
            console.log(quantidade);
        });
    })        
</script>
    
asked by anonymous 26.04.2016 / 21:55

1 answer

0

In a WebAPI, you do not need to receive the data as object and then deserialize. The System.Web.WebAPI namespace already has Newtonsoft.Json as a dependency that solves this for you.

Try this:

public async Task<int> ContarPessoas([FromBody] Pessoa[] familia)
{
    return familia.Length;
}

Important Know that if the data does not satisfy the input parameter, the request will be made, and the input value will be null .

And you also do not need this setup in global.asax . Try setting nothing, sound everything default , it should work.

    
26.04.2016 / 22:25