Grails v3.3.3, JSON Views 1.2.7: переполнение стека при выполнении глубокого рендеринга родительского объекта

получение переполнения стека при глубоком рендеринге объекта домена с представлениями json.

Я создал объект домена Клиента и объект домена Сайты, где у Клиента много сайтов.

Я создал двух клиентов и один сайт в bootsrap и назначил сайт первому клиенту.

Я использовал службу данных gorm для создания экземпляра customerService из интерфейса.

В соответствии с документацией по представлениям json (раздел 2.5.2) я заверил, что когда я использую действие индекса на контроллере, я выполняю выборку соединения для сайтов и гарантирую, что у меня есть список клиентов и сайты в одном сеансе гибернации для перехода к Посмотреть.

в представлении _customerRest.gson при вызове «json g.render (customer, [deep:true])». У меня есть шаблон _siteRest.gson для сопряжения объекта/контроллера домена сайта (это работает, когда я просто использую rest для получения сайтов или конкретного сайта, который отображается правильно)

когда я вызываю свой uri '/api/customers' с Postman, я получаю повторяющийся вывод json, подобный этому

[{"id":1,"sites":[{"id":1,"name":"Canary Wharf","customer":{"id":1,"sites":[{"id":1,"name":"Canary Wharf","customer":{"id":1,"sites":[{"id":1,"name":"Canary Wharf","customer":{"id":1,"sites":[{"id":1,"name":"Canary Wharf","customer":{"id":1,"sites":[{"id":1,"name":"Canary Wharf","customer":{"id":1,"sites":[{"id":1,"name":"Canary Wharf","customer":{"id":1,"sites":[{"id":1,"name":"Canary Wharf","customer":{"id":1,"sites":[{"id":1,"name":"Canary Wharf","customer":{"id":1,"sites":[{"id":1,"name":"Canary Wharf","customer":{"id":1,"sites":[{"id":1,"name":"Canary Wharf","customer":{"id":1,"sites":[{"id":1,"name":"Canary Wharf","customer":{"id":1,"sites":[{"id":1,"name":"Canary Wharf","customer":{"id":1...

а затем такой stackoverflow

Grails application running at http://localhost:8080 in environment: development
CustomerRest.customerRest.index method invoked
param map to customerService contains [controller:customerRest, action:index, max:10, fetch:[sites:join]]
service returned collection  [ttrestapi.model.Customer : 1, ttrestapi.model.Customer : 2]
2018-03-20 16:36:21.504 ERROR --- [nio-8080-exec-4] o.g.web.errors.GrailsExceptionResolver   : StackOverflowError occurred when processing request: [GET] /api/customers
Stacktrace follows:

java.lang.reflect.InvocationTargetException: null
    at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:211)
    at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:188)
    at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
    at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
    at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
    at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Caused by: grails.views.ViewRenderException: Error rendering view: Error rendering view: null
    at grails.views.AbstractWritableScript.writeTo(AbstractWritableScript.groovy:43)
    at grails.views.mvc.GenericGroovyTemplateView.renderMergedOutputModel(GenericGroovyTemplateView.groovy:73)
    at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:303)
    at grails.views.mvc.renderer.DefaultViewRenderer.render(DefaultViewRenderer.groovy:111)
    at grails.artefact.controller.RestResponder$Trait$Helper.internalRespond(RestResponder.groovy:188)
    at grails.artefact.controller.RestResponder$Trait$Helper.respond(RestResponder.groovy:98)
    at ttrestapi.model.CustomerRestController.index(CustomerRestController.groovy:27)
    ... 14 common frames omitted
Caused by: grails.views.ViewRenderException: Error rendering view: null
    at grails.views.AbstractWritableScript.writeTo(AbstractWritableScript.groovy:43)
    at grails.plugin.json.view.api.internal.DefaultGrailsJsonViewHelper$6.writeTo(DefaultGrailsJsonViewHelper.groovy:792)
    at grails.plugin.json.view.JsonViewWritableScript.json(JsonViewWritableScript.groovy:123)
    at grails.plugin.json.view.JsonViewWritableScript.json(JsonViewWritableScript.groovy:146)
    at ttRestApi_customerRest_index_gson.run(ttRestApi_customerRest_index_gson:9)
    at grails.plugin.json.view.JsonViewWritableScript.doWrite(JsonViewWritableScript.groovy:28)
    at grails.views.AbstractWritableScript.writeTo(AbstractWritableScript.groovy:40)
    ... 20 common frames omitted
Caused by: java.lang.StackOverflowError: null
    at java.io.InputStream.<init>(InputStream.java:45)
    at java.util.zip.ZipFile$ZipFileInputStream.<init>(ZipFile.java:711)
    at java.util.zip.ZipFile.getInputStream(ZipFile.java:375)
    at java.util.jar.JarFile.getInputStream(JarFile.java:447)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:454)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at grails.plugin.json.builder.StreamingJsonBuilder$StreamingJsonDelegate.curryDelegateAndGetContent(StreamingJsonBuilder.java:809)
    at grails.plugin.json.builder.StreamingJsonBuilder$StreamingJsonDelegate.writeObject(StreamingJsonBuilder.java:775)
    at grails.plugin.json.builder.StreamingJsonBuilder$StreamingJsonDelegate.writeCollectionWithClosure(StreamingJsonBuilder.java:766)
    at grails.plugin.json.builder.StreamingJsonBuilder$StreamingJsonDelegate.writeObjects(StreamingJsonBuilder.java:706)
    at grails.plugin.json.builder.StreamingJsonBuilder$StreamingJsonDelegate.call(StreamingJsonBuilder.java:610)
    at .....

если я удалю параметр [deep:true], мой json будет отображаться, но без отображения сведений о сайте в выводе - только идентификатор сайта как дочерний.

последняя версия этого примера проекта находится здесь на github: https://github.com/woodmawa/ttRestApi

у кого-нибудь еще была эта проблема, это ошибка в моем шаблоне gson где-то?

PS. если изменить команду рендеринга на json g.render (customer , [expand:['sites']]) - это работает правильно, как и следовало ожидать. но опция [deep:true] не работает, как показано выше.

мое действие индекса в CustomerRestController выглядит так с операторами отладки в нем

    def index(Integer max) {
        println "CustomerRest.customerRest.index method invoked"
        params.max = Math.min(max ?: 10, 100)
        params.put("fetch", [sites:"join"])   //force join fetch on sites
        println "param map to customerService contains $params"

        Collection result = customerService.list(params)
        println "service returned collection  $result"

 //       respond customerService.list(params), [ttrestapi.model:[customerCount: customerService.count()], view:"fred"]
        respond result, [model:[customerCount: customerService.count()]]
    }

когда я смотрю на результат запроса customerService.list, у меня есть первый клиент и его единственный сайт через запрос fetch:[sites:"join"] как и ожидалось

мой _customerRest.gson выглядит так

    import ttrestapi.model.Customer

    model {
        Customer customer
    }

    json g.render (customer , [deep:true])

и соответствующие сайты _siteRest.gson выглядят так

import ttrestapi.model.Site

model {
    Site site
}

json jsonapi.render (site)

person WILLIAM WOODMAN    schedule 20.03.2018    source источник
comment
Ваши вопросы такие длинные, а пример приложения включает в себя слишком много несвязанных вещей, и все это затрудняет понимание того, что происходит, но я провел достаточно времени, разбираясь в том, что делает ваше приложение, чтобы увидеть, что это выглядит как ошибка. Я думаю, что пример проекта связан с github.com/grails/grails-views/issues/165. с ним, вероятно, легче работать.   -  person Jeff Scott Brown    schedule 21.03.2018


Ответы (1)


Я отправил отчет об ошибке по адресу https://github.com/grails/grails-views/issues/165.

Шаблон на https://github.com/jeffbrown/williamwoodman/blob/86b0bed52fd00bb09cee22e49234797ef44f3a35/grails-app/views/customer/_customer.gson выглядит так:

import williamwoodman.Customer

model {
    Customer customer
}

json g.render(customer, [deep: true])

Это вызовет ошибку. В зависимости от того, как вы хотите, чтобы отображаемый JSON выглядел, у вас есть несколько вариантов. Одна вещь, которую вы можете сделать, это создать свой шаблон, чтобы он выглядел примерно так:

import williamwoodman.Customer

model {
    Customer customer
}

json {
    id customer.id
    name customer.name
    sites g.render(customer.sites ?: [])
}

В этом примере проекта изменение шаблона, чтобы он выглядел так, а затем отправка запроса на http://localhost:8080/customer даст JSON, который выглядит так:

[{"id":1,"name":"Customer One",
  "sites":[{"id":1,"name":"Site One","customer":{"id":1}},       
           {"id":3,"name":"Site Three","customer":{"id":1}},
           {"id":2,"name":"Site Two","customer":{"id":1}}]}]

Возможно, вы захотите исключить customer из отдельных карт site, а можете и нет.

Надеюсь, это поможет.

person Jeff Scott Brown    schedule 21.03.2018
comment
Конечно, вместо sites g.render(customer.sites ?: []) вы можете отобразить шаблон _site.gson и определить там представление Site и т. д. У вас есть несколько вариантов в зависимости от того, что вы хотите сгенерировать. ХТН - person Jeff Scott Brown; 21.03.2018
comment
Джефф извиняется за сложность/длину вопроса, но в итоге я обошел так много вариантов, «что, если я попробую это» и т. д. и т. д., поэтому проект на git отразил это. Я хотел попытаться использовать ответы в формате jsonapi с минимальными изменениями ручного рендеринга от меня. Спасибо, что подняли ошибку от моего имени - никогда не уверен, что это я иногда тупой или что-то реальное. В настоящее время я буду практиковаться с некоторыми из указанных параметров, пока не будет выполнено исправление. - person WILLIAM WOODMAN; 21.03.2018
comment
PS в подотношениях «сайты» вам не нужен указатель обратной точки «клиент: {id: 1}», поскольку он неявно исходит от родителя. поэтому в этом случае вы надеетесь, что процесс рендеринга для родителя может распознать это при вызове шаблона _site.gson для рендеринга дочернего элемента. Я не уверен, что вспомогательный класс jsonapi.render() справится с этим автоматически, поэтому не уверен, как указать шаблон по умолчанию для вызова дочернего элемента при вызове jsonapi.render() родительского объекта. - person WILLIAM WOODMAN; 21.03.2018