For now, JSON entry types in contentful are managed like local JSON. You have to know the structure of your JSON to query every bit of it that you need. IMO JSON entry types in Contentful serves another purpose. If I know exactly what fields I need I would just create text fields. I would like to use JSON fields as returning a plain old JS object without the need to query inside it.
A pretty straightforward example: Imagine I have a content type for pages. I could add a JSON field for css styles specific to that page. Obviously the properties in my JSON entry will be different for every page.
Anyone a thought about this? A counter-argument maybe?
I was faced with the same use case when doing gatsby-source-wordpress.
In my scenario that wouldn't be the styles but the page contents itself that would be dynamic, changing from page to page, so making it impossible to query it using GraphQL. I ended up with creating a new GraphQL node of type application/json child-linked to the source node and which contains the stringyfied version of the JSON. Then I query it in GraphQL, and call JSON.Parse() to retrieve the "unique" datamodel.
I suppose that keeping that entry of type JSON as a string is a good idea. Maybe someone have another use case ?
Thanks @sebastienfi . glad to know I am not the only one facing this kind of issue.
@Khaledgarbaya can you think of a use case where it would NOT be a good idea to stringify JSON entry types?
I can imagine a content type with a complex structure. It may be easier to copy/paste a json as is than create a bunch of text fields. But since it is a matter of compromise anyway, user could as well fetch the whole json object in that case...
We can also try and make both usages coexist. Maybe put a flag somewhere or decide a specific property name to be used if user wants its content to be stringified...?
Hey @MarcCoet most of the time a JSON field is used as a configuration for another library etc. I don't see people interacting with the JSON data directly, they will be pulling it and pass it along to another lib/framework etc.
Ok, so you agree it makes sense to pull json fields as string in gatsby?
Yeah It makes sense to get it as a String
@Khaledgarbaya @MarcCoet @sebastienfi I ran into this issue as well and created a PR #2967 with a "make both usages coexist" solution (although I agree needing a stringified version is the more common use case). If you have time to look it over, I'd be interested in hearing your thoughts.
@sarahatwork curious why you'd do translations as a JSON string instead of using Contentful's localization features? Seems like that'd just complicate your life? I'm sure there's reasons but generally Gatsby (and Contentful) push people towards structured data as that simplifies a lot of things.
@KyleAMathews This seemed like a good solution for "utility strings" that don't correspond to module instances, like the words "Close" and "Menu." They're things I'd normally save locally as some kind of language bundle, but since I'm using Contentful anyway, I figured it'd be best to put all my strings in one place, so updating translations won't require a new code deploy.
I'm using Contentful's localization features for everything else- for example I have a BodyText content type, and the main content field is localized so editors can include localized copy.
Hi @sarahatwork, your solution looks great my only concern is having this internal.content just for the JSON Node which can create some inconsistency
@Khaledgarbaya It's already a property created for text nodes: https://github.com/gatsbyjs/gatsby/pull/2967/files#diff-0f3299a5d7eb6ef91bdf3a22e5059e0fR153
But I'm definitely open for moving/renaming it!
It might be helpful to look at the two functions together:
// existing function
function createTextNode(node, key, text, createNode) {
const str = _.isString(text) ? text : ` `
const textNode = {
id: `${node.id}${key}TextNode`,
parent: node.id,
children: [],
[key]: str,
internal: {
type: _.camelCase(`${node.internal.type} ${key} TextNode`),
mediaType: `text/markdown`,
content: str,
contentDigest: digest(str),
},
}
node.children = node.children.concat([textNode.id])
createNode(textNode)
return textNode.id
}
// added by me
function createJSONNode(node, key, content, createNode) {
const str = JSON.stringify(content)
const JSONNode = {
// I decided to spread `content` rather than using `[key]: content,` like above,
// since that felt like that added an extra query layer, but it's something we could do.
// It would prevent name collisions.
...content,
id: `${node.id}${key}JSONNode`,
parent: node.id,
children: [],
internal: {
type: _.camelCase(`${node.internal.type} ${key} JSONNode`),
mediaType: `application/json`,
content: str,
contentDigest: digest(str),
},
}
node.children = node.children.concat([JSONNode.id])
createNode(JSONNode)
return JSONNode.id
}
@sarahatwork ah yeah, sorry I missed that
So that's how you get a PR approved... Thanks for the lesson @sarahatwork . ;)
Nice touch using intenal.content. Thanks for moving this forward. I'll close this issue as soon as the PR gets merged.
thanks everyone!
Most helpful comment
So that's how you get a PR approved... Thanks for the lesson @sarahatwork . ;)
Nice touch using
intenal.content. Thanks for moving this forward. I'll close this issue as soon as the PR gets merged.