[MGNLRESTCL-86] Hard to get content from JsonNode object in FTL Created: 21/Oct/19  Updated: 31/Dec/19  Resolved: 16/Dec/19

Status: Closed
Project: REST Client
Component/s: None
Affects Version/s: None
Fix Version/s: 2.0

Type: Improvement Priority: Major
Reporter: Christopher Zimmermann Assignee: Quach Hao Thien
Resolution: Fixed Votes: 0
Labels: None
Remaining Estimate: 0d
Time Spent: 3.75d
Original Estimate: Not Specified

Issue Links:
causality
is causing MGNLRESTCL-118 DOC: Converting JsonNode to Map in Fr... Closed
Template:
Acceptance criteria:
Empty
Task DoD:
[ ]* Doc/release notes changes? Comment present?
[ ]* Downstream builds green?
[ ]* Solution information and context easily available?
[ ]* Tests
[ ]* FixVersion filled and not yet released
[ ]  Architecture Decision Record (ADR)
Documentation update required:
Yes
Date of First Response:
Epic Link: Declarative REST clients
Sprint: Declarative REST 12
Story Points: 5

 Description   

User Story:

As a template developer I want to work with content from restfn in the same way that I work with content from the other magnolia templating libaries (cmsfn, catfn...), so that I only have to learn things once and can apply that knowledge elsewhere and therefore be more productive.

Acceptance Criteria:

  • Get content from the rest client in a format similar to cmsfn functions.
  • Easy to access, simple syntax
  • cmsfn.dump() function outputs useful, expected content.

 

 

Context and Problem description

In order for RestClients to be used in Apps, Link fields and Findbar resolvers, they must use the JsonNode entityClass. However it is hard to work with JsonNode in FTL.

  • If you use cmsfn.dump(), it just outputs: "ObjectNode (#1500182346)", instead of a helpful map of all contained content.
  • To use it in freemarker, you need to use a bunch of .getItems() calls, which is unexpected and different from the 'normal' Magnolia context and content objects.
// With JsonNode
[#assign title = item.get("fields").get("title").textValue()]
[#assign authors = item.get("fields").get("authors").get(0).textValue()]
[#assign story = item.get("fields").get("story").get("content").get(0).get("content").get(0).get("value").textValue()]

An alternative is to set entityClass to java.util.HashMap on the restClient call. Then cmsfn.dump() gets content as expected.

// With HashMap
[#assign title = item.fields.title]
[#assign authors = item.fields.authors[0]]
[#assign story = item.fields.story.content[0].content[0].value]

However, if you use the HashMap entity class, then that restClient call is incompatible with the app, link field, findbar resolver etc.

 

Note:

Some possible solutions 

  1. Templating function to convert JsonNodes into Maps, e.g. restfn.asContentMap
  2. and/or templating function taking a JsonPath expression as input to resolve sub-objects, e.g. restfn.read equivalent of JsonPath.read
    • can't JsonPath#read be used directly in FTLs?
    • also how hard is it to dump the raw Json content from FTL straight, without conversion? Do we need a restfn.dump at all?
  3. Create new datasource that can handle maps. (Maybe mapDatasource?) (Introduce Map propertySet that vaadin could understand.)
  4. Use JsonObject instead of JsonNode. See MGNLUI-5316; not actively considered for now (contribute to JsonPath or fork the library)
  5. Add a parameter to restfn.call to get the content as a map.
  6. Add a new function to restfn, something like restfn.callMap(), restfn.callAndGetMap()

 

Another workaround is to then always to create 2 calls on the restClient, one for the FTL and one for the other things. But that is unpleasant and harder to understand for a developer, even with the fancy YAML shortcuts.



 Comments   
Comment by Mikaël Geljić [ 12/Nov/19 ]

mdrapela , in particular an equivalent of that JSONFactoryImpl#looseDeserialize (formerly looseDeserializeSafe) sounds tempting; could parse to JSON-P's JsonObject straight.

Comment by Quach Hao Thien [ 06/Dec/19 ]

hi czimmermann, could you please provide the restClient was used in this ticket? I need it for testing

Comment by Christopher Zimmermann [ 09/Dec/19 ]

Sure.

So here is the FTL I have that is using it:

https://git.magnolia-cms.com/users/czimmermann/repos/restclient-testing/browse/light-modules/rc-contentful-demo/templates/components/cf.ftl

And here is the REST Client: 
https://git.magnolia-cms.com/users/czimmermann/repos/restclient-testing/browse/light-modules/rc-contentful-demo/restClients/contentfulSimple.yaml

(You can see I had two different calls configured using the different entityClass')

Comment by Quach Hao Thien [ 11/Dec/19 ]

Hi czimmermann,

as I observed, the problem of cannot access jsonNode by children elements in your example 
item.get("fields").get("title").textValue()
but not as you expect
item.fields.title
 because item is a JsonNode, and this datatype have a method calls fields() which is a Iterator<Map.Entry<String, JsonNode>>, therefore it is vague between the fields and fields() leads to exception when you call item.fields, but you can overcome this by using

item.get("fields").title

Do you think it solve your problem?

Comment by Christopher Zimmermann [ 11/Dec/19 ]

thien.quach THanks, I did not know that. That only slightly helps though, and therefore it does not solve the problem. Developers should be able to use the same syntax that they were familar with from maps. (Examples in "Context and Problem description" above.)

Comment by Quach Hao Thien [ 12/Dec/19 ]

yes, I'll provide one method to convert JsonNode to Map, and you can use it by

[#assign itemMap = restfn.asMap(items)/]
${itemMap.items[0].fields.title}

=> Kayaking is Kool

and then dump it as well

${cmsfn.dump(itemMap, 5, true)}

=>
Hash (6)
  total = 2 (Integer)
  limit = 100 (Integer)
  skip = 0 (Integer)
  includes = Hash (1)
    Asset = Sequence (2)
      0 = Hash (2)
        sys = Hash (8)
...
Comment by Christopher Zimmermann [ 12/Dec/19 ]

Looks good!

Generated at Mon Feb 12 10:43:05 CET 2024 using Jira 9.4.2#940002-sha1:46d1a51de284217efdcb32434eab47a99af2938b.