You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Grails 5 DeepDomainClassMarshaller处理JSON循环引用与空值问题

Grails 5 领域类转JSON系列问题解决方案

环境配置

grailsVersion=5.3.2
grailsGradlePluginVersion=5.3.0
groovyVersion=3.0.11
gorm.version=7.3.3

涉及领域类:Company、User、ZApp(类间存在双向关联)


问题1:深度转换触发循环引用错误

配置:设置grails.converters.json.default.deep = true
异常信息:

Caused by: org.grails.web.converters.exceptions.ConverterException: java.lang.StringIndexOutOfBoundsException: start 0, end -1, length 0

原因:领域类双向关联导致JSON序列化时出现循环引用,默认逻辑无法处理。
解决:添加配置启用循环引用的PATH处理:

grails.converters.json.circular.reference.behaviour='PATH'

问题2:空值字段触发JSON序列错误

异常信息:

Caused by: org.grails.web.converters.exceptions.ConverterException: org.grails.web.json.JSONException: Value out of sequence: expected mode to be OBJECT or ARRAY when writing 'org.grails.web.json.JSONWriter$NullWritable@1a1fd03d' but was INIT

原因:PATH模式下的JSONWriter对空值的处理逻辑存在兼容性问题,直接输出空值违反了JSON写入状态。
解决:自定义DomainMarshaller显式处理空值:

import grails.converters.JSON
import org.grails.web.json.JSONWriter

class CustomDomainMarshaller implements JSONMarshaller {
    @Override
    void marshalObject(Object o, JSON json) {
        JSONWriter writer = json.getWriter()
        writer.object()
        o.class.declaredFields.each { field ->
            field.accessible = true
            def value = field.get(o)
            writer.key(field.name)
            if (value == null) {
                writer.value(null)
            } else {
                json.convertAnother(value)
            }
        }
        writer.endObject()
    }
}

在BootStrap.groovy注册Marshaller:

JSON.registerMarshaller(Company, new CustomDomainMarshaller())
JSON.registerMarshaller(User, new CustomDomainMarshaller())
JSON.registerMarshaller(ZApp, new CustomDomainMarshaller())

问题3:HasMany关联触发类型转换异常

异常信息:

Caused by: java.lang.ClassCastException: class org.grails.web.json.PathCapturingJSONWriterWrapper$PropertyElement cannot be cast to class org.grails.web.json.PathCapturingJSONWriterWrapper$IndexElement

触发条件:Company的apps(hasMany关联)包含2个及以上元素时触发,返回JSON格式异常。
原因:自定义Marshaller未适配PATH模式下PathCapturingJSONWriterWrapper的状态管理,集合序列化时错误将属性元素当作索引元素处理。
最终解决方案:

  1. 推荐方案:改用IGNORE模式处理循环引用,避免PATH模式的兼容问题:

    grails.converters.json.circular.reference.behaviour='IGNORE'
    

    该模式会自动忽略循环引用的字段,稳定性更高。

  2. 保留PATH模式的修复:修改自定义Marshaller,显式处理集合类型:

    @Override
     void marshalObject(Object o, JSON json) {
         JSONWriter writer = json.getWriter()
         writer.object()
         o.class.declaredFields.each { field ->
             field.accessible = true
             def value = field.get(o)
             writer.key(field.name)
             if (value == null) {
                 writer.value(null)
             } else if (value instanceof Collection) {
                 writer.array()
                 value.each { item ->
                     json.convertAnother(item)
                 }
                 writer.endArray()
             } else {
                 json.convertAnother(value)
             }
         }
         writer.endObject()
     }
    

    通过显式开启/结束数组,确保JSONWriter状态正确。

  3. 替代方案:使用grails-datastore-rest插件,它对关联和空值的处理更健壮:
    在build.gradle引入插件:

    implementation "org.grails.plugins:grails-datastore-rest:7.3.3"
    

    可通过领域类静态映射配置序列化规则:

    class Company {
        static mapping = {
            json include: ['name', 'agentId', 'apps'], deep: true
        }
    }
    

内容的提问来源于stack exchange,提问作者Chetan Sharma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 12:03:24