interface CMArticle @inherit(from: ["CMTeasable"]) {
linkedArticles: [CMArticle]
}
Resolving Linked Content in Structs in the Headless Server - CMCC 12
Learn how to resolve linked content in structs
What you'll learn
- Getting content from linked content in structs
Prerequisites
- A CoreMedia system with Headless Server
Time matters
Should I read this?
The settings struct field of the CMLinkable type provides access to settings of content elements. However when retrieving content items or lists of content items from those structs, these elements are not resolved. Instead, the corresponding content ID is returned.
The reason for this limitation is that the Headless Server doesn’t return a proper GraphQL type as response but instead simple JSON. Resolving content elements in these JSON responses would raise the question which fields of the content item should get displayed. Displaying all fields of a content item does not effectively work and undermines all of GraphQLs advantages. Furthermore changing these mechanisms requires considerably more extensive and project specific adaptions to the JSON parsing.
Solution
In order to solve the problem, you have to step away from the JSON responses and use a combination of the StructAdapter
and the GraphQL type system.
Via the `StructAdapter`it is possible to retrieve the values in the specified struct. If described correctly in the GraphQL schema, GraphQL takes care of returning the proper fields for a given content element. For the type specification of the fields, the built-in types as well as custom ones may be used.
Linked Content Elements
In order to resolve linked lists, two components are needed.
-
The actual link list needs to be retrieved. This can be done via a new field in the GraphQL Schema and a corresponding fetch declaration.
-
The field needs to be declared in the actual type as well as in the interface. When doing this you should use the
StructAdapter::getStructValue()
Method of theStructAdapter
. This is necessary for GraphQL to be able to interpret the results and use the correct fields.
An important note is that all the types which are supposed to get retrieved from the struct need to be defined as GraphQL types.
In this example, there is a LinkList of articles defined in the property customArticles in the localSettings of an CMArticle.
type CMArticleImpl implements CMArticle @inherit(from: ["CMTeasableImpl"]) {
linkedArticles: [CMArticle] @fetch(from: "@structAdapter.to(#root).getStructValue('localSettings',{'test', 'customArticles'}, null)")
}
This enables the linked content items to be queried like this:
{
content {
article(id: "15580") {
id
name
linkedArticles {
id
name
detailText
}
}
}
}
Special case CMSettings:
Retrieving a (linked) result of the CMSettings type can be a special case, since the values you might want to retrieve from a CMSettings document can themselves be content items in structs which need to get resolved.
In this case you also need to repeat the same procedure: create a new field in the CMSettings interface to specify the structure of data to be returned by the field and specify in the CMSettings type how to retrieve the data for the type. When just accessing primitive values in CMSettings, you can use the existing settings field to retrieve these values.
interface CMArticle @inherit(from: ["CMTeasable"]) {
customSettings: [MySetting]
}
type CMArticleImpl implements CMArticle @inherit(from: ["CMTeasableImpl"]) {
customSettings: [MySetting] @fetch(from: "@structAdapter.to(#root).getStructValue('linkedSettings',{}, null)")
}
type MySetting @inherit(from: ["CMSettingsImpl"]){
settingsList: [CMPicture] @fetch(from: "@structAdapter.to(#root).getStructValue('settings', {'linkedPictures'}, null)")
}
This makes the code of the query a bit more complex, since you also need to take care of this level:
{
content {
article(id: "15580") {
id
name
customSettings {
settingsList {
id
name
uriTemplate
}
}
}
}
}
General Remarks
The previous examples focus on the more generic use-case of linked content items in structs. This is especially useful if there are other values in custom fields of the content.
If the values are just in settings, it is also possible to use the SettingsAdapter
. This has the additional advantage of not needing to specify if the setting in question is in the local settings or linked settings.
It can be used to simplify, for example, the way to retrieve linked content items in linked settings. When you use the SettingsAdapter
to resolve the value, you can directly retrieve the values without the additional step of custom fields in the CMSettings type:
interface CMArticle @inherit(from: ["CMTeasable"]) {
customSettings: [CMPicture]
}
type CMArticleImpl implements CMArticle @inherit(from: ["CMTeasableImpl"]) {
customSettings: [CMPicture] @fetch(from: "@settingsAdapter.to(#root).get({'linkedPictures'})")
}
And in the query:
{
content {
article(id: "15580") {
id
name
customSettings {
id
name
uriTemplate
}
}
}
}
Another aspect relates to the specificity of the types. While it makes sense to specify (if known) the exact types of the results in the schema, you can use the full bandwidth of GraphQLs capabilities here. An example would be the usage of the generic Content_ type in combination with the GraphQL spread operator:
interface CMArticle @inherit(from: ["CMTeasable"]) {
customSettings: [Content_]
}
type CMArticleImpl implements CMArticle @inherit(from: ["CMTeasableImpl"]) {
customSettings: [Content_] @fetch(from: "@settingsAdapter.to(#root).get({'linkedContentItems'})")
}
{
content {
article(id: "15580") {
id
name
customSettings {
... on CMPicture {
id
name
uriTemplate
}
... on CMArticle {
id
name
detailText
}
}
}
}
}
This way, you can dynamically decide in the query which type is expected. This can also be combined with a parametrized field to specify the path to the properties in question, similar to the existing settings field in CMLinkable or CMSettings.
type CMArticleImpl implements CMArticle @inherit(from: ["CMTeasableImpl"]) {
customSettings(path: [String]!): [Content_] @fetch(from: "@settingsAdapter.to(#root).get({#path})")
}
And this can be used in the query like that:
{
content {
article(id: "15580") {
id
name
customSettings(path: ["linkedContentItems"]) {
... on CMPicture {
id
name
uriTemplate
}
... on CMArticle {
id
name
detailText
}
}
}
}
}
When using this however you can not query multiple different properties at once like in the settings property of the CMSettings and CMLinkable types, since here you might get conflicts if their contents have different types.
Limitations
Of course, this approach only works if the fields in the structs actually have the correct types. When requesting a CMArticle from a CMPicture you will get errors about type incompatibility.