Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): Added support for multi-template inheritance. #426

Merged
merged 2 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/src/docs/asciidoc/json/history.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

The current release is {version}. Below is a version history.

For later releases check the release notes at: https://github.com/grails/grails-views/releases

*2.0.2*

* Fix bug where incorrect JSON was generated when all values are null in the nested object.
Expand Down
9 changes: 9 additions & 0 deletions json/grails-app/views/_child2MultipleParents.gson
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import grails.plugin.json.view.Player
import groovy.transform.Field

inherits(template: 'parent2')
inherits(template: 'parent4')

@Field Player player

json g.render(player)
9 changes: 9 additions & 0 deletions json/grails-app/views/_child3MultipleParents.gson
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import grails.plugin.json.view.Player
import groovy.transform.Field

inherits(template: 'parent3')
inherits(template: 'parent4')

@Field Player player

json g.render(player, [includes:'name'])
11 changes: 11 additions & 0 deletions json/grails-app/views/_child4MultipleParents.gson
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import grails.plugin.json.view.Player
import groovy.transform.Field

inherits(template: 'parent2')
inherits(template: 'parent4')

@Field Player player

json {
name player.name
}
7 changes: 7 additions & 0 deletions json/grails-app/views/_parent4.gson
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import groovy.transform.Field

@Field Object object

json {
bar "foo"
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import grails.plugin.json.builder.JsonOutput
import grails.plugin.json.builder.StreamingJsonBuilder
import grails.plugin.json.view.api.JsonView
import grails.plugin.json.view.api.internal.DefaultGrailsJsonViewHelper
import grails.plugin.json.view.api.internal.ParentInfo
import grails.util.GrailsNameUtils
import grails.views.AbstractWritableScript
import grails.views.GrailsViewTemplate
import grails.views.api.GrailsView
import groovy.transform.CompileStatic
import org.grails.buffer.FastStringWriter
Expand Down Expand Up @@ -46,13 +48,20 @@ abstract class JsonViewWritableScript extends AbstractWritableScript implements
* @return
*/
StreamingJsonBuilder json(@DelegatesTo(value = StreamingJsonBuilder.StreamingJsonDelegate, strategy = Closure.DELEGATE_FIRST) Closure callable) {
if(parentTemplate != null) {
if(parentData.size() > 0) {
if (!inline) {
out.write(JsonOutput.OPEN_BRACE)
}
def parentWritable = prepareParentWritable()
parentWritable.writeTo(out)
resetProcessedObjects()
Iterator parentInfoIt = parentData.iterator()
while ( parentInfoIt.hasNext() ) {
ParentInfo parentInfo = parentInfoIt.next()
def parentWritable = prepareParentWritable(parentInfo.parentTemplate, parentInfo.parentModel)
parentWritable.writeTo(out)
resetProcessedObjects()
if ( parentInfoIt.hasNext() ) {
out.write(JsonOutput.COMMA)
}
}
def jsonDelegate = new StreamingJsonBuilder.StreamingJsonDelegate(out, false, generator)
callable.setDelegate(jsonDelegate)
callable.call()
Expand Down Expand Up @@ -107,13 +116,20 @@ abstract class JsonViewWritableScript extends AbstractWritableScript implements
* @return The json builder
*/
StreamingJsonBuilder json(JsonOutput.JsonWritable writable) {
if(parentTemplate != null) {
if(parentData.size() > 0) {
if (!inline) {
out.write(JsonOutput.OPEN_BRACE)
}
def parentWritable = prepareParentWritable()
parentWritable.writeTo(out)
resetProcessedObjects()
Iterator parentInfoIt = parentData.iterator()
while ( parentInfoIt.hasNext() ) {
ParentInfo parentInfo = parentInfoIt.next()
def parentWritable = prepareParentWritable(parentInfo.parentTemplate, parentInfo.parentModel)
parentWritable.writeTo(out)
resetProcessedObjects()
if ( parentInfoIt.hasNext() ) {
out.write(JsonOutput.COMMA)
}
}
writable.setInline(true)
writable.setFirst(false)
writable.writeTo(out)
Expand Down Expand Up @@ -158,7 +174,7 @@ abstract class JsonViewWritableScript extends AbstractWritableScript implements
return json
}

private GrailsView prepareParentWritable() {
private GrailsView prepareParentWritable(GrailsViewTemplate parentTemplate, Map parentModel) {
parentModel.putAll(binding.variables)
for(o in binding.variables.values()) {
if (o != null) {
Expand Down
19 changes: 7 additions & 12 deletions json/src/main/groovy/grails/plugin/json/view/api/JsonView.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import grails.plugin.json.builder.StreamingJsonBuilder
import grails.plugin.json.view.api.internal.DefaultGrailsJsonViewHelper
import grails.plugin.json.view.api.internal.DefaultHalViewHelper
import grails.plugin.json.view.api.internal.DefaultJsonApiViewHelper
import grails.plugin.json.view.api.internal.ParentInfo
import grails.plugin.json.view.api.internal.TemplateRenderer
import grails.plugin.json.view.api.jsonapi.JsonApiIdRenderStrategy
import grails.views.GrailsViewTemplate
Expand All @@ -21,7 +22,6 @@ import groovy.transform.CompileStatic
*/
@CompileStatic
trait JsonView extends GrailsView {

/**
* The default generator
*/
Expand All @@ -37,15 +37,7 @@ trait JsonView extends GrailsView {
*/
StreamingJsonBuilder json

/**
* The parent template if any
*/
GrailsViewTemplate parentTemplate

/**
* The parent model, if any
*/
Map parentModel
Collection<ParentInfo> parentData = []
/**
* Overrides the default helper with new methods specific to JSON building
*/
Expand Down Expand Up @@ -91,8 +83,11 @@ trait JsonView extends GrailsView {
.resolveTemplateUri(getControllerNamespace(), getControllerName(), template.toString())
GrailsViewTemplate parentTemplate = (GrailsViewTemplate)templateEngine.resolveTemplate(templateUri, locale)
if(parentTemplate != null) {
this.parentTemplate = parentTemplate
this.parentModel = model
ParentInfo parentInfo = new ParentInfo(
parentTemplate: parentTemplate,
parentModel: model
)
parentData.add(parentInfo)
}
else {
throw new ViewException("Template not found for name $template")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package grails.plugin.json.view.api.internal

import grails.views.GrailsViewTemplate
import groovy.transform.CompileStatic

@CompileStatic
class ParentInfo {
/**
* The parent template if any
*/
GrailsViewTemplate parentTemplate

/**
* The parent model, if any
*/
Map parentModel
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,31 @@ class TemplateInheritanceSpec extends Specification implements JsonViewTest {
result.jsonText == '{"name":"Cantona"}'
}

void "test extending multiple templates"() {
when:

def result = render(template:'child2MultipleParents', model:[player: new Player(name: "Cantona")])
then:
result.jsonText == '{"_links":{"self":{"href":"http://localhost:8080/player","hreflang":"en","type":"application/hal+json"}},"foo":"bar","bar":"foo","name":"Cantona"}'
}

void "test extending multiple templates that uses g.render(..)"() {

when:
def player = new Player(name: "Cantona")
player.id = 1L
def result = render(template:'child3MultipleParents', model:[player: player])
then:
result.jsonText == '{"id":1,"bar":"foo","name":"Cantona"}'
}

void "test extending multiple templates and rendering a JSON block"() {
when:

def result = render(template:'child4MultipleParents', model:[player: new Player(name: "Cantona")])
then:
result.jsonText == '{"_links":{"self":{"href":"http://localhost:8080/player","hreflang":"en","type":"application/hal+json"}},"foo":"bar","bar":"foo","name":"Cantona"}'
}


}