The API endpoint I'm working with is returning data that has multiple nested relationships inside it, and I am using normalizeResponse() within DS.JSONAPISerializer to massage it into something that is fully JSON-API compliant.
The ember inspector shows that all data gets placed within its respective container correctly. The link between the top-level model and its hasMany child does work, but the link between the nested models does not work. I verify this by navigating within the inspector to the nested model's child model, clicking on it, and observing that its 'content' property is null.
First, take a look at how my models are set up:
// models/search.js
// i am able to browse from the search model to children with success
export default DS.Model.extend({
articles: DS.hasMany('article'),
});
// models/article.js
// i CANNOT browse from an article down to its digest in ember inspector
export default DS.Model.extend({
search: DS.belongsTo('search'),
type: DS.attr(),
created: DS.attr(),
updated: DS.attr(),
digest: DS.belongsTo('digest'),
});
// models/digest.js
export default DS.Model.extend({
title: DS.attr(),
desc: DS.attr(),
date: DS.attr(),
article: DS.belongsTo('article'),
});
Now, here's my modified JSON after my functions inside normalizeResponse complete. AFTER returning this data from normalizeResponse, the "digest" object under the parent "relationships" object disappears. Is there something wrong with my JSON? I've tried so many permutations of this with no success, and I am pretty sure this matches the JSON-API spec for Compound Documents.
{"data":{
"type":"searches",
"id":"17482738723",
"attributes":{
},
"relationships":{
"articles":{
"data":[
{
"type":"articles",
"id":"19988"
},
{
"type":"articles",
"id":"19989"
},
]
},
"digest":{
"data":[
{
"type":"digest",
"id":"19988_digest"
},
{
"type":"digest",
"id":"19989_digest"
},
]
}
}
},
"included":[
{
"id":"19988",
"type":"articles",
"attributes":{
"type": "internal",
"created":"2016-09-27T00:13:11.000Z",
"updated":"2016-09-27T00:13:11.000Z",
}
},
{
"id":"19988_digest",
"type":"digest",
"attributes":{
"title":null,
"desc":"four five six",
}
},
{
"id":"19989",
"type":"articles",
"attributes":{
"type": "internal",
"created":"2016-09-27T00:13:11.000Z",
"updated":"2016-09-27T00:13:11.000Z",
}
},
{
"id":"19989_digest",
"type":"digest",
"attributes":{
"title":"one two three",
"desc":null,
}
},
]
}
Your response indicates the following relationship model:
// models/search.js
export default DS.Model.extend({
articles: DS.hasMany('article'),
dignists: DS.hasMany('digest'),
});
// models/article.js
export default DS.Model.extend({
search: DS.belongsTo('search'),
});
// models/digest.js
export default DS.Model.extend({
search: DS.belongsTo('search'),
});
So you have to fix your response:
remove the digest relationship on the search
add a digest relationship to every article
So you will end with something like this:
{
"data":{
"type":"searches",
"id":"17482738723",
"attributes":{
},
"relationships":{
"articles":{
"data":[
{
"type":"articles",
"id":"19988"
},
{
"type":"articles",
"id":"19989"
},
]
}
}
},
"included":[
{
"id":"19988",
"type":"articles",
"attributes":{
"type": "internal",
"created":"2016-09-27T00:13:11.000Z",
"updated":"2016-09-27T00:13:11.000Z",
},
"relationships":{
"digest": {
"data": {
"type":"digest",
"id":"19988_digest"
}
}
}
},
{
"id":"19988_digest",
"type":"digest",
"attributes":{
"title":null,
"desc":"four five six",
},
"relationships":{
"digest":{
"data": {
"type":"digest",
"id":"19989_digest"
}
}
}
},
{
"id":"19989",
"type":"articles",
"attributes":{
"type": "internal",
"created":"2016-09-27T00:13:11.000Z",
"updated":"2016-09-27T00:13:11.000Z",
}
},
{
"id":"19989_digest",
"type":"digest",
"attributes":{
"title":"one two three",
"desc":null,
}
},
]
}
Know that you can also do it the other way around and specify the article on the digest. ember-data will automatically keep everything in sync. I personally prefer to specify both sides of the relationship for clarity.
Related
I am using datatables and here is how my data looks like:
{
"data": [{
"request": {
"responsible": "Pete Jackson",
"valuta": " EUR",
"customer": "Jim Manner",
"office": "123 Houston",
"UNID": "9D574D34B9140D3CC1257B8E002A487E"
}
}, {
"request": {
"responsible": "Jane Awesome",
"valuta": " EUR",
"customer": "Christian Slater",
"office": "503 New York",
"UNID": "2444DAA352E89A44C1257B8E002A487F"
}
}]
}
The datatables columns I have defined as followed:
'columns': [{
data: 'request.office',
'render': function(data) {
return data;
}
}, {
data: 'request.responsible',
'render': function(data) {
return data;
}
}, {
data: 'request.customer',
'render': function(data) {
return data;
}
}
]
Now I want to apply rowGrouping according the following example that I have found: http://live.datatables.net/migixiqi/1/edit
However it uses the rows for grouping and its seems the columns defined as dataSrc are considered as objects cause I get 'No group' returned as row group label.
How can I send in a real value as source in the rowgroup definition instead of the (expected) column value?
Perhaps I dont understand what you are hoping to do, but you can just pass the JSON path to the rowGroup.dataSrc exactly as you do with columns.data :
rowGroup: {
dataSrc: 'request.customer' //just a guess you want to group by custumer
},
http://jsfiddle.net/tgsz78jk/
PS: render() callbacks are unnecessary unless you actually need to do something special with a columns content, sort, filter or search behaviour.
The examples here don't go nearly far enough in explaining how to produce a more complicated structure...
If I want to end up with something like:
{
"data": {
"type": "mobile_screens",
"id": "1",
"attributes": {
"title": "Watch"
},
"relationships": {
"mobile_screen_components": {
"data": [
{
"id": "1_1",
"type": "mobile_screen_components"
},
{
"id": "1_2",
"type": "mobile_screen_components"
},
...
]
}
}
},
"included": [
{
"id": "1_1",
"type": "mobile_screen_components",
"attributes": {
"title": "Featured Playlist",
"display_type": "shelf"
},
"relationships": {
"playlist": {
"data": {
"id": "938973798001",
"type": "playlists"
}
}
}
},
{
"id": "938973798001",
"type": "playlists",
"relationships": {
"videos": {
"data": [
{
"id": "5536725488001",
"type": "videos"
},
{
"id": "5535943875001",
"type": "videos"
}
]
}
}
},
{
"id": "5536725488001",
"type": "videos",
"attributes": {
"duration": 78321,
"live_stream": false,
"thumbnail": {
"width": 1280,
"url":
"http://xxx.jpg?pubId=694940094001",
"height": 720
},
"last_published_date": "2017-08-09T18:26:04.899Z",
"streams": [
{
"url":
"http://xxx.m3u8",
"mime_type": "MP4"
}
],
"last_modified_date": "2017-08-09T18:26:27.621Z",
"description": "xxx",
"fn__media_tags": [
"weather",
"personality"
],
"created_date": "2017-08-09T18:23:16.830Z",
"title": "NOAA predicts most active hurricane season since 2010",
"fn__tve_authentication_required": false
}
},
...,
]
}
what is the most simple data structure and serializer I can set up?
I get stumped after something like:
const mobile_screen_components = responses.map((currentValue, index) => {
id[`id_${index}`];
});
const dataSet = {
id: 1,
title: 'Watch',
mobile_screen_components,
};
const ScreenSerializer = new JSONAPISerializer('mobile_screens', {
attributes: ['title', 'mobile_screen_components'],
mobile_screen_components: {
ref: 'id',
}
});
Which only gives me:
{
"data": {
"type": "mobile_screens",
"id": "1",
"attributes": { "title": "Watch" },
"relationships": {
"mobile-screen-components": {
"data": [
{ "type": "mobile_screen_components", "id": "1_0" },
{ "type": "mobile_screen_components", "id": "1_1" },
{ "type": "mobile_screen_components", "id": "1_2" },
{ "type": "mobile_screen_components", "id": "1_3" },
{ "type": "mobile_screen_components", "id": "1_4" },
{ "type": "mobile_screen_components", "id": "1_5" }
]
}
}
}
}
I have no idea how to get the "included" sibling to "data." etc.
So, the question is:
what is the most simple data structure and serializer I can set up?
Below is the simplest object that can be converted to JSON similar to JSON in the question using jsonapi-serializer:
let dataSet = {
id: '1',
title: 'Watch',
mobile_screen_components: [
{
id: '1_1',
title: 'Featured Playlists',
display_type: 'shelf',
playlists: {
id: 938973798001,
videos: [
{
id: 5536725488001,
duration: 78321,
live_stream: false
},
{
id: 5535943875001,
duration: 52621,
live_stream: true
}
]
}
}
]
};
To serialize this object to JSON API, I used the following code:
let json = new JSONAPISerializer('mobile_screen', {
attributes: ['id', 'title', 'mobile_screen_components'],
mobile_screen_components: {
ref: 'id',
attributes: ['id', 'title', 'display_type', 'playlists'],
playlists: {
ref: 'id',
attributes: ['id', 'videos'],
videos: {
ref: 'id',
attributes: ['id', 'duration', 'live_stream']
}
}
}
}).serialize(dataSet);
console.log(JSON.stringify(json, null, 2));
The first parameter of JSONAPISerializer constructor is the resource type.
The second parameter is the serialization options.
Each level of the options equals to the level of the nested object in serialized object.
ref - if present, it's considered as a relationships.
attributes - an array of attributes to show.
Introduction
First of all we have to understand the JSON API document data structure
[0.1] Refering to the top level (object root keys) :
A document MUST contain at least one of the following top-level
members:
data: the document’s “primary data”
errors: an array of error objects
meta: a meta object that contains non-standard meta-information.
A document MAY contain any of these top-level members:
jsonapi: an object describing the server’s implementation
links: a links object related to the primary data.
included: an array of resource objects that are related to the primary data and/or each other (“included resources”).
[0.2]
The document’s “primary data” is a representation of the resource or
collection of resources targeted by a request.
Primary data MUST be either:
a single resource identifier object, or
null, for requests that target single resources
an array of resource identifier
objects, or an empty array ([]), for reqs. that target
collections
Example
The following primary data is a single resource object:
{
"data": {
"type": "articles",
"id": "1",
"attributes": {
// ... this article's attributes
},
"relationships": {
// ... this article's relationships
}
}
}
In the (jsonapi-serializer) documentation : Available serialization option (opts argument)
So in order to add the included (top-level member) I performed the following test :
var JsonApiSerializer = require('jsonapi-serializer').Serializer;
const DATASET = {
id:23,title:'Lifestyle',slug:'lifestyle',
subcategories: [
{description:'Practices for becoming 31337.',id:1337,title:'Elite'},
{description:'Practices for health.',id:69,title:'Vitality'}
]
}
const TEMPLATE = {
topLevelLinks:{self:'http://example.com'},
dataLinks:{self:function(collection){return 'http://example.com/'+collection.id}},
attributes:['title','slug','subcategories'],
subcategories:{ref:'id',attributes:['id','title','description']}
}
let SERIALIZER = new JsonApiSerializer('pratices', DATASET, TEMPLATE)
console.log(SERIALIZER)
With the following output :
{ links: { self: 'http://example.com' },
included:
[ { type: 'subcategories', id: '1337', attributes: [Object] },
{ type: 'subcategories', id: '69', attributes: [Object] } ],
data:
{ type: 'pratices',
id: '23',
links: { self: 'http://example.com/23' },
attributes: { title: 'Lifestyle', slug: 'lifestyle' },
relationships: { subcategories: [Object] } } }
As you may observe, the included is correctly populated.
NOTE : If you need more help with your dataSet, edit your question with the original data.
I have the following json api document:
{
"data": [
{
"type": "haves",
"id": "2708f443-0857-4ae9-9935-9aa4b4e9f721",
"attributes": {
"quantity": 1
},
"relationships": {
"card": {
"data": {
"type": "cards",
"id": "3be08f31-3361-404c-9977-23535ed837f3"
}
}
}
}
],
"included": [
{
"type": "cards",
"id": "3be08f31-3361-404c-9977-23535ed837f3",
"attributes": {
"name": "Name"
},
"relationships": {
"set": {
"data": {
"type": "sets",
"id": "0fec70de-02e0-4646-bdcf-f86acea90d23"
}
}
}
},
{
"type": "sets",
"id": "0fec70de-02e0-4646-bdcf-f86acea90d23",
"attributes": {
"name": "Name"
}
}
]
}
With the following ember models:
// app/models/have.js
export default DS.Model.extend({
quantity: DS.attr('number'),
minPrice: DS.attr('number'),
account: DS.belongsTo('account'),
card: DS.belongsTo('card')
});
// app/models/set.js
export default DS.Model.extend({
name: DS.attr('string'),
cards: DS.hasMany('card')
});
// app/models/card.js
export default DS.Model.extend({
name: DS.attr('string'),
set: DS.belongsTo('set'),
haves: DS.hasMany('have')
});
And a custom inflector rule:
inflector.irregular('have', 'haves');
When I load the json document with this structure though, I can't seem to do something like have.card.set.name in my template when I iterate this jsonapi document. I'm guessing my jsonapi structure is incorrect. What am I missing? I don't get any errors in my chrome console or in the ember server running. When I load Ember Inspector, I see the set model under Data.
From the perspective of a consumer, is there any value in abstracting resource attributes to make the fields self-describing? Or should the documentation handle it.
The idea is that each attribute will be wrapped in a more complex object which will provide fieldId, fieldType, and the value. Making each field more descriptive.
In addition, the web service would include another endpoint to further describe each field.
So, instead of the following:
{
"id":123,
"type":"person",
"attributes":{
"name":"John Smith",
"dateOfBirth":"2000-01-01",
"ssn":123456789
}
}
The json would look like this:
{
"id":123,
"type":"person",
"attributes":[
{
"fieldId":"name",
"dataType":"string",
"value":"John Smith"
},
{
"fieldId":"dateOfBirth",
"dataType":"date",
"value":"2000-01-01"
},
{
"fieldId":"ssn",
"dataType":"integer",
"value":123456789
}
],
"relationships":{
"dataType":{
"links":{
"related":{
"href":"http://acme.com/ws/dataTypes/"
}
},
"data":[
{
"id":"string",
"type":"dataType"
},
{
"id":"date",
"type":"dataType"
},
{
"id":"integer",
"type":"dataType"
}
]
},
"field":{
"links":{
"related":{
"href":"http://acme.com/ws/fields/"
}
},
"data":[
{
"id":"name",
"type":"field"
},
{
"id":"dateOfBirth",
"type":"field"
},
{
"id":"ssn",
"type":"field"
}
]
}
}
}
And then a dataType resource linked to would give some description and/or format:
{
"id":"ssn",
"type":"field",
"attributes":{
"valueType":"string",
"description":"Social security in the xxx-xx-xxxx format."
},
"links":{
"self":{
"href":"http://acme.com/ws/fields/ssn",
"meta":{
"httpMethod":"GET"
}
}
}
}
{
"id":"date",
"type":"dataType",
"attributes":{
"valueType":"string",
"description":"yyyy-MM-dd"
},
"links":{
"self":{
"href":"http://acme.com/ws/dataTypes/date",
"meta":{
"httpMethod":"GET"
}
}
}
}
To answer this From the perspective of a consumer, is there any value in abstracting resource attributes to make the fields self-describing? Or should the documentation handle it.
Based on experience and evaluating multiple api's the api should only send required data. There is no point handling description in response that needs to be taken care by documentation.
Plus consider the extra amount of data you are sending just to describe the fields
In addition frontend (say javascript) would need to parse the object, save time by sending only the required data
consider the bandwidth taken by this
{
"id":123,
"type":"person",
"attributes":{
"name":"John Smith",
"dateOfBirth":"2000-01-01",
"ssn":123456789
}
}
as compared to this huge data
{
"id":123,
"type":"person",
"attributes":[
{
"fieldId":"name",
"dataType":"string",
"value":"John Smith"
},
{
"fieldId":"dateOfBirth",
"dataType":"date",
"value":"2000-01-01"
},
{
"fieldId":"ssn",
"dataType":"integer",
"value":123456789
}
],
"relationships":{
"dataType":{
"links":{
"related":{
"href":"http://acme.com/ws/dataTypes/"
}
},
"data":[
{
"id":"string",
"type":"dataType"
},
{
"id":"date",
"type":"dataType"
},
{
"id":"integer",
"type":"dataType"
}
]
},
"field":{
"links":{
"related":{
"href":"http://acme.com/ws/fields/"
}
},
"data":[
{
"id":"name",
"type":"field"
},
{
"id":"dateOfBirth",
"type":"field"
},
{
"id":"ssn",
"type":"field"
}
]
}
}
}
From consumer perspective provide them only the required data in response and description in documentation.
And don't make separate call for providing more details, it will be very hard to maintain if you ever change version
I am having a JSON data like below.
{
"divisions": [{
"name": "division1",
"id": "div1",
"subdivisions": [{
"name": "Sub1Div1",
"id": "div1sub1",
"schemes": [{
"name": "Scheme1",
"id": "scheme1"
}, {
"name": "Scheme2",
"id": "scheme2"
}]
}, {
"name": "Sub2Div1",
"id": "div1sub2",
"schemes": [{
"name": "Scheme3",
"id": "scheme3"
}]
}
]
}]
}
I want to read this into a TreeStore, but cannot change the subfields ( divisions, subdivisions, schemes ) to be the same (eg, children).
How can achieve I this?
When nested JSON is loaded into a TreeStore, essentially the children nodes are loaded through a recursive calls between TreeStore.fillNode() method and NodeInterface.appendChild().
The actual retrieval of each node's children field is done within TreeStore.onNodeAdded() on this line:
dataRoot = reader.getRoot(data);
The getRoot() of the reader is dynamically created in the reader's buildExtractors() method, which is what you'll need to override in order to deal with varying children fields within nested JSON. Here is how it's done:
Ext.define('MyVariJsonReader', {
extend: 'Ext.data.reader.Json',
alias : 'reader.varijson',
buildExtractors : function()
{
var me = this;
me.callParent(arguments);
me.getRoot = function ( aObj ) {
// Special cases
switch( aObj.name )
{
case 'Bill': return aObj[ 'children' ];
case 'Norman': return aObj[ 'sons' ];
}
// Default root is `people`
return aObj[ 'people' ];
};
}
});
This will be able to interpret such JSON:
{
"people":[
{
"name":"Bill",
"expanded":true,
"children":[
{
"name":"Kate",
"leaf":true
},
{
"name":"John",
"leaf":true
}
]
},
{
"name":"Norman",
"expanded":true,
"sons":[
{
"name":"Mike",
"leaf":true
},
{
"name":"Harry",
"leaf":true
}
]
}
]
}
See this JsFiddle for fully working code.