I was able to resolve the "$ref" links in my json code by adapting the code, this blogger posted, to be Angular compliant.
http://willseitz-code.blogspot.com/2013/01/javascript-to-deserialize-json-that.html
I created an angular service to resolve the "$ref" I get from NewtonSofts JSON serializer.
// http://willseitz-code.blogspot.com/2013/01/javascript-to-deserialize-json-that.html
(function () {
'use strict';
angular
.module('app')
.factory('jsonPointerParseService', jsonPointerParseService);
jsonPointerParseService.$inject = [];
function jsonPointerParseService() {
var hashOfObjects = {};
var service = {
pointerParse: pointerParse
};
return service;
function collectIds(obj) {
if (jQuery.type(obj) === "object") {
if (obj.hasOwnProperty("$id")) {
hashOfObjects[obj.$id] = obj;
}
for (var prop in obj) {
collectIds(obj[prop]);
}
} else if (jQuery.type(obj) === "array") {
obj.forEach(function (element) {
collectIds(element);
});
}
}
function setReferences(obj) {
if (jQuery.type(obj) === "object") {
for (var prop in obj) {
if (jQuery.type(obj[prop]) === "object" &&
obj[prop].hasOwnProperty("$ref")) {
obj[prop] = hashOfObjects[obj[prop]["$ref"]];
} else {
setReferences(obj[prop]);
}
}
} else if (jQuery.type(obj) === "array") {
obj.forEach(function (element, index, array) {
if (jQuery.type(element) === "object" &&
element.hasOwnProperty("$ref")) {
array[index] = hashOfObjects[element["$ref"]];
} else {
setReferences(element);
}
});
}
}
// Set the max depth of your object graph because JSON.stringify will not be able to
// serialize a large object graph back to
function setMaxDepth(obj, depth) {
// If this is not an object or array just return there is no need to
// set its max depth.
if (jQuery.type(obj) !== "array" && jQuery.type(obj) !== "object") {
return obj;
}
var newObj = {};
// If this object was an array we want to return the same type in order
// to keep this variable's consistency.
if (jQuery.type(obj) === "array") {
newObj = [];
}
// For each object or array cut off its tree at the depth value by
// recursively diving into the tree.
angular.forEach(obj, function (value, key) {
if (depth == 1) {
newObj = null;
}
else if (jQuery.type(value) === "array") {
if (setMaxDepth(value, depth - 1)) {
newObj[key] = setMaxDepth(value, depth - 1)
} else {
newObj = [];
}
} else if (jQuery.type(value) === "object") {
if (setMaxDepth(value, depth - 1)) {
newObj[key] = setMaxDepth(value, depth - 1)
} else {
newObj = [];
}
} else {
newObj[key] = value;
}
}, newObj);
return newObj;
}
function pointerParse(obj, depth) {
var newObj = obj;
hashOfObjects = {};
collectIds(newObj);
setReferences(newObj);
if (depth) {
newObj = setMaxDepth(newObj, depth);
}
return newObj;
}
}
})();
Usage would be like so:
var newObj = jsonPointerParseService.pointerParse(obj);
Please keep in mind, this is an Angular service. So I inject it into whichever controller needs to have these references resolved, and make the call to pointerParse() accordingly.
Please note though that if you want to post this object back to the server, your browsers JSON.stringify() call has to be able to handle an object with circular references. By default, in my case, Angular does not call JSON.stringify() with the parameters required to be able to stringify an object with circular references. So, in this case I would suggest posting only a small portion of data and not this entire object back or instantiating your own call to stringify an object like this.
Lastly, I must say that dealing with $ref is a pain and I decided to preserve reference handling with only arrays. This seemed to work well for me. I still had to access array values with $values however I didnt have to worry about resolving references and didnt have to worry about the server blowing up trying to serialize all of the object circular references to json.
json.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Arrays;
Update: Even with only resolving arrays there are still references that you may need to resolve. In those instances use the pointerParse() function to take care of them.
Another alternative (using HATEOAS). This is simple, mostly in practice you add a links tag in the json depending on your use of HATEOAS.
http://api.example.com/games/1
:
{
"id": 1,
"title": "Game A",
"publisher": "Publisher ABC",
"developer": "Developer DEF",
"releaseDate": "2015-01-01",
"platforms": [
{"_self": "http://api.example.com/games/1/platforms/53", "name": "Playstation"},
{"_self": "http://api.example.com/games/1/platforms/34", "name": "Xbox"},
]
}
http://api.example.com/games/1/platforms/34
:
{
"id": 34,
"title": "Xbox",
"publisher": "Microsoft",
"releaseDate": "2015-01-01",
"testReport": "http://api.example.com/games/1/platforms/34/reports/84848.pdf",
"forms": [
{"type": "edit", "fields: [] },
]
}
You can off course embed all data in all listing but that will likely be way too much data. This way you can embed the required data and then load more if you really want to work with it.
The technical implementation can contain caching. You can cache the platforms links and names in the game object and send it instantly without having to load the platforms api at all. Then when required you can load it.
You see for example that I added some form information. I did that to show you there can be much more information in a detailed json object than you would even want to load in the listing of games.
Best Answer
When you are sending an
accept
header requesting a specific media type, the server should not send back something else, and most certainly not with a 200 OK status codeFrom Restpatterns.org:
(Emphasis mine)
Restpatterns.org takes this from the actual HTTP standard: Header field definitions - Accept
In short: you are not being pedantic. The services are not following the HTTP standard if they are returning HTML when the accept header specifically tells them to return
application/json
and nothing else.