Skip to content

Commit

Permalink
Add option for rendering nulls
Browse files Browse the repository at this point in the history
  • Loading branch information
jameskleeh committed Aug 21, 2017
1 parent 00ac397 commit 3977f2c
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 23 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ classes/
.idea/
.idea_modules
build/
**/out/
*.iml
*.ipr
*.iws
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,18 @@ class DefaultGrailsJsonViewHelper extends DefaultJsonViewHelper implements Grail

List<String> incs = getIncludes(arguments)
List<String> excs = getExcludes(arguments)
boolean renderNulls = getRenderNulls(arguments)

def mappingContext = jsonView.mappingContext
object = mappingContext.proxyHandler.unwrap(object)

PersistentEntity entity = mappingContext.getPersistentEntity(object.getClass().name)

if(entity != null) {
process(jsonDelegate, entity, object, processedObjects, incs, excs, "", isDeep, expandProperties, includeAssociations, customizer)
process(jsonDelegate, entity, object, processedObjects, incs, excs, "", isDeep, renderNulls, expandProperties, includeAssociations, customizer)
}
else {
processSimple(jsonDelegate, object, processedObjects, incs, excs, "", customizer)
processSimple(jsonDelegate, object, processedObjects, incs, excs, "", renderNulls, customizer)
}
}

Expand Down Expand Up @@ -160,13 +161,15 @@ class DefaultGrailsJsonViewHelper extends DefaultJsonViewHelper implements Grail
final boolean isDeep = ViewUtils.getBooleanFromMap(DEEP, arguments)
List<String> expandProperties = getExpandProperties(jsonView, arguments)
final Closure beforeClosure = (Closure)arguments.get(BEFORE_CLOSURE)
boolean renderNulls = getRenderNulls(arguments)


Closure doProcessEntity = { StreamingJsonBuilder.StreamingJsonDelegate jsonDelegate, List<String> incs, List<String> excs ->
process(jsonDelegate, entity, object, processedObjects, incs, excs, path, isDeep, expandProperties, true, customizer)
process(jsonDelegate, entity, object, processedObjects, incs, excs, path, isDeep, renderNulls, expandProperties, true, customizer)
}

Closure doProcessSimple = { StreamingJsonBuilder.StreamingJsonDelegate jsonDelegate, List<String> incs, List<String> excs ->
processSimple(jsonDelegate, object, processedObjects, incs, excs, path, customizer)
processSimple(jsonDelegate, object, processedObjects, incs, excs, path, renderNulls, customizer)
}

JsonGenerator generator = getGenerator()
Expand Down Expand Up @@ -327,10 +330,10 @@ class DefaultGrailsJsonViewHelper extends DefaultJsonViewHelper implements Grail
JsonGenerator generator = getGenerator()
Map<Object, JsonOutput.JsonWritable> processedObjects = initializeProcessedObjects(binding)
if(object instanceof Iterable) {
return getIterableWritable(object, arguments, customizer, processedObjects)
return getIterableWritable((Iterable)object, arguments, customizer, processedObjects)
}
else if(object instanceof Map) {
return getMapWritable(object, arguments, customizer, processedObjects)
return getMapWritable((Map)object, arguments, customizer, processedObjects)
}
else if(object instanceof Throwable) {
Throwable e = (Throwable)object
Expand Down Expand Up @@ -364,7 +367,7 @@ class DefaultGrailsJsonViewHelper extends DefaultJsonViewHelper implements Grail
processedObjects
}

protected void processSimple(StreamingJsonBuilder.StreamingJsonDelegate jsonDelegate, Object object, Map<Object, JsonOutput.JsonWritable> processedObjects, List<String> incs, List<String> excs, String path, Closure customizer = null) {
protected void processSimple(StreamingJsonBuilder.StreamingJsonDelegate jsonDelegate, Object object, Map<Object, JsonOutput.JsonWritable> processedObjects, List<String> incs, List<String> excs, String path, Boolean renderNulls, Closure customizer = null) {

if(!processedObjects.containsKey(object)) {
processedObjects.put(object, NULL_OUTPUT)
Expand Down Expand Up @@ -413,7 +416,7 @@ class DefaultGrailsJsonViewHelper extends DefaultJsonViewHelper implements Grail
}
else {
out.append JsonOutput.OPEN_BRACE
processSimple(new StreamingJsonDelegate(out, true), o, processedObjects, incs, excs,"${path}${propertyName}.")
processSimple(new StreamingJsonDelegate(out, true), o, processedObjects, incs, excs,"${path}${propertyName}.", renderNulls)
out.append JsonOutput.CLOSE_BRACE
}
})
Expand All @@ -428,13 +431,16 @@ class DefaultGrailsJsonViewHelper extends DefaultJsonViewHelper implements Grail
else {
jsonDelegate.call( propertyName ) {
jsonDelegate = (StreamingJsonBuilder.StreamingJsonDelegate)getDelegate()
processSimple(jsonDelegate, value, processedObjects, incs, excs,"${path}${propertyName}.")
processSimple(jsonDelegate, value, processedObjects, incs, excs,"${path}${propertyName}.", renderNulls)
}
}

}
}
}
else if (renderNulls) {
jsonDelegate.call(propertyName, NULL_OUTPUT)
}
}
}
}
Expand Down Expand Up @@ -469,7 +475,7 @@ class DefaultGrailsJsonViewHelper extends DefaultJsonViewHelper implements Grail

}

protected void process(StreamingJsonBuilder.StreamingJsonDelegate jsonDelegate, PersistentEntity entity, Object object, Map<Object, JsonOutput.JsonWritable> processedObjects, List<String> incs, List<String> excs, String path, boolean isDeep, List<String> expandProperties = [], boolean includeAssociations = true, Closure customizer = null) {
protected void process(StreamingJsonBuilder.StreamingJsonDelegate jsonDelegate, PersistentEntity entity, Object object, Map<Object, JsonOutput.JsonWritable> processedObjects, List<String> incs, List<String> excs, String path, boolean isDeep, boolean renderNulls, List<String> expandProperties = [], boolean includeAssociations = true, Closure customizer = null) {

/*
if(processedObjects.containsKey(object)) {
Expand All @@ -481,15 +487,20 @@ class DefaultGrailsJsonViewHelper extends DefaultJsonViewHelper implements Grail
ResolvableGroovyTemplateEngine templateEngine = view.templateEngine
Locale locale = view.locale

renderEntityId(jsonDelegate, processedObjects, incs, excs, path, isDeep, expandProperties, getValidIdProperties(entity, object, incs, excs, path))
renderEntityId(jsonDelegate, processedObjects, incs, excs, path, isDeep, renderNulls, expandProperties, getValidIdProperties(entity, object, incs, excs, path))

for (prop in entity.persistentProperties) {
def propertyName = prop.name
String qualified = "${path}${propertyName}"
if (!includeExcludeSupport.shouldInclude(incs, excs, qualified)) continue

def value = ((GroovyObject) object).getProperty(propertyName)
if(value == null) continue
if(value == null) {
if (renderNulls) {
jsonDelegate.call(propertyName, NULL_OUTPUT)
}
continue
}

if (!(prop instanceof Association)) {
processSimpleProperty(jsonDelegate, (PersistentProperty) prop, propertyName, value)
Expand All @@ -507,10 +518,10 @@ class DefaultGrailsJsonViewHelper extends DefaultJsonViewHelper implements Grail
jsonDelegate.call(propertyName) {
StreamingJsonBuilder.StreamingJsonDelegate embeddedDelegate = (StreamingJsonBuilder.StreamingJsonDelegate)getDelegate()
if(associatedEntity != null) {
process(embeddedDelegate, associatedEntity,value, processedObjects, incs, excs , "${qualified}.", isDeep)
process(embeddedDelegate, associatedEntity,value, processedObjects, incs, excs , "${qualified}.", isDeep, renderNulls)
}
else {
processSimple(embeddedDelegate, value, processedObjects, incs, excs, "${qualified}.")
processSimple(embeddedDelegate, value, processedObjects, incs, excs, "${qualified}.", renderNulls)
}
}
}
Expand All @@ -531,7 +542,8 @@ class DefaultGrailsJsonViewHelper extends DefaultJsonViewHelper implements Grail
else {
jsonDelegate.call(propertyName) {
StreamingJsonBuilder.StreamingJsonDelegate embeddedDelegate = (StreamingJsonBuilder.StreamingJsonDelegate)getDelegate()
process(embeddedDelegate, associatedEntity,value, processedObjects, incs, excs , "${qualified}.", isDeep, expandProperties)

process(embeddedDelegate, associatedEntity,value, processedObjects, incs, excs , "${qualified}.", isDeep, renderNulls, expandProperties)
}
}

Expand All @@ -540,7 +552,7 @@ class DefaultGrailsJsonViewHelper extends DefaultJsonViewHelper implements Grail
Map validIdProperties = getValidIdProperties(associatedEntity, value, incs, excs, "${qualified}.")
if (validIdProperties.size() > 0) {
jsonDelegate.call(propertyName) {
renderEntityId(delegate, processedObjects, incs, excs, "${qualified}.", isDeep, expandProperties, validIdProperties)
renderEntityId(delegate, processedObjects, incs, excs, "${qualified}.", isDeep, renderNulls, expandProperties, validIdProperties)
}
}
}
Expand Down Expand Up @@ -590,17 +602,17 @@ class DefaultGrailsJsonViewHelper extends DefaultJsonViewHelper implements Grail
} else {
jsonDelegate.call(propertyName, (Iterable)value) { child ->
StreamingJsonBuilder.StreamingJsonDelegate embeddedDelegate = (StreamingJsonBuilder.StreamingJsonDelegate)getDelegate()
process(embeddedDelegate, associatedEntity,child, processedObjects, incs, excs , "${qualified}.", isDeep)
process(embeddedDelegate, associatedEntity,child, processedObjects, incs, excs , "${qualified}.", isDeep, renderNulls)
}
}
} else {
jsonDelegate.call(propertyName, (Iterable)value) { child ->
Map idProperties = getValidIdProperties(associatedEntity, child, incs, excs, "${qualified}.")
if (idProperties.size() > 0) {
renderEntityId(getDelegate(), processedObjects, incs, excs, "${qualified}.", isDeep, expandProperties, idProperties)
renderEntityId(getDelegate(), processedObjects, incs, excs, "${qualified}.", isDeep, renderNulls, expandProperties, idProperties)
} else {
StreamingJsonBuilder.StreamingJsonDelegate embeddedDelegate = (StreamingJsonBuilder.StreamingJsonDelegate)getDelegate()
process(embeddedDelegate, associatedEntity,child, processedObjects, incs, excs , "${qualified}.", isDeep, expandProperties)
process(embeddedDelegate, associatedEntity,child, processedObjects, incs, excs , "${qualified}.", isDeep, renderNulls, expandProperties)
}
}
}
Expand All @@ -610,7 +622,7 @@ class DefaultGrailsJsonViewHelper extends DefaultJsonViewHelper implements Grail
if(Iterable.isAssignableFrom(ass.type) && associatedEntity != null) {
jsonDelegate.call(propertyName, (Iterable)value) { child ->
StreamingJsonBuilder.StreamingJsonDelegate embeddedDelegate = (StreamingJsonBuilder.StreamingJsonDelegate)getDelegate()
process(embeddedDelegate, associatedEntity,child, processedObjects, incs, excs , "${qualified}.", isDeep, expandProperties)
process(embeddedDelegate, associatedEntity,child, processedObjects, incs, excs , "${qualified}.", isDeep, renderNulls, expandProperties)
}
}
}
Expand Down Expand Up @@ -676,7 +688,7 @@ class DefaultGrailsJsonViewHelper extends DefaultJsonViewHelper implements Grail
ids
}

private renderEntityId(StreamingJsonBuilder.StreamingJsonDelegate jsonDelegate, Map<Object, JsonOutput.JsonWritable> processedObjects, List<String> incs, List<String> excs, String path, boolean isDeep, List<String> expandProperties, Map<PersistentProperty, Object> idProperties) {
private renderEntityId(StreamingJsonBuilder.StreamingJsonDelegate jsonDelegate, Map<Object, JsonOutput.JsonWritable> processedObjects, List<String> incs, List<String> excs, String path, boolean isDeep, boolean renderNulls, List<String> expandProperties, Map<PersistentProperty, Object> idProperties) {

idProperties.each { PersistentProperty property, Object idValue ->
def idType = property.type
Expand All @@ -692,11 +704,11 @@ class DefaultGrailsJsonViewHelper extends DefaultJsonViewHelper implements Grail
if(!ass.circular && (isDeep || expandProperties.contains(idQualified))) {
jsonDelegate.call(idName) {
StreamingJsonBuilder.StreamingJsonDelegate embeddedDelegate = (StreamingJsonBuilder.StreamingJsonDelegate)getDelegate()
process(embeddedDelegate, ass.associatedEntity, idValue, processedObjects, incs, excs , "${idQualified}.", isDeep, expandProperties)
process(embeddedDelegate, ass.associatedEntity, idValue, processedObjects, incs, excs , "${idQualified}.", isDeep, renderNulls, expandProperties)
}
} else {
jsonDelegate.call(idName) {
renderEntityId(getDelegate(), processedObjects, incs, excs, "${idQualified}.", isDeep, expandProperties, getValidIdProperties(ass.associatedEntity, idValue, incs, excs, "${idQualified}."))
renderEntityId(getDelegate(), processedObjects, incs, excs, "${idQualified}.", isDeep, renderNulls, expandProperties, getValidIdProperties(ass.associatedEntity, idValue, incs, excs, "${idQualified}."))
}
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ class DefaultJsonViewHelper extends DefaultGrailsViewHelper {
ViewUtils.getStringListFromMap(IncludeExcludeSupport.EXCLUDES_PROPERTY, arguments)
}

Boolean getRenderNulls(Map arguments) {
ViewUtils.getBooleanFromMap('renderNulls', arguments, false)
}

protected PersistentEntity findEntity(Object object) {
def clazz = object.getClass()
try {
Expand Down
101 changes: 101 additions & 0 deletions json/src/test/groovy/grails/plugin/json/view/NullRenderingSpec.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package grails.plugin.json.view

import grails.plugin.json.view.test.JsonViewTest
import spock.lang.Specification

class NullRenderingSpec extends Specification implements JsonViewTest {

void "test rendering nulls with a domain"() {
given:
def templateText = '''
import grails.plugin.json.view.*
model {
Player player
}
json g.render(player)
'''

when:
mappingContext.addPersistentEntity(Player)
def renderResult = render(templateText, [player: new Player()])

then:"No fields are rendered because they are null"
renderResult.jsonText == '{}'
}

void "test rendering nulls with a domain (renderNulls = true)"() {
given:
def templateText = '''
import grails.plugin.json.view.*
model {
Player player
}
json g.render(player, [renderNulls: true])
'''
when:
mappingContext.addPersistentEntity(Player)
def renderResult = render(templateText, [player: new Player()])
then:"No fields are rendered because they are null"
renderResult.jsonText == '{"name":null,"team":null}'
}
void "test rendering nulls with a map"() {
given:
def templateText = '''
model {
Map map
}
json g.render(map)
'''
when:
mappingContext.addPersistentEntity(Player)
def renderResult = render(templateText, [map: [foo: null, bar: null]])
then:"Maps with nulls are rendered by default"
renderResult.jsonText == '{"foo":null,"bar":null}'
}
void "test rendering nulls with a pogo"() {
given:
def templateText = '''
model {
Object obj
}
json g.render(obj)
'''
when:
mappingContext.addPersistentEntity(Player)
def renderResult = render(templateText, [obj: new Child2()])
then:"No fields are rendered because they are null"
renderResult.jsonText == '{}'
}
void "test rendering nulls with a pogo (renderNulls = true)"() {
given:
def templateText = '''
model {
Object obj
}
json g.render(obj, [renderNulls: true])
'''
when:
mappingContext.addPersistentEntity(Player)
def renderResult = render(templateText, [obj: new Child2()])
then:
renderResult.jsonText == '{"name":null,"parent":null}'
}
}

0 comments on commit 3977f2c

Please sign in to comment.