Robert Lohr
Robert Lohr

Reputation: 635

Groovy/Grails Proliferation of "\" escape character in a string

This is a rather odd issue. I'm integration testing a Grails service and the associated domain class. One property of that domain class is a String that holds a JSON. The database field is json too and there's a custom Hibernate value type that performs the necessary conversion. It's already been working for years in production in another domain class.

class MyDomain {
    String data
    static mapping = {
        data type: StringJsonUserType
    }
}

So far so good. In my test I mock an input object to my service method that ultimately will contain and return the desired JSON string.

private MockedClass mockClass() {
    // JsonRepresentable declares asJson() method.
    def data = GroovyMock(JsonRepresentable)
    data.asJson() >> "{\"content\":\"irrelevant\"}"

    def mockClass = GroovyMock(MockedClass)
    mockClass.getData() >> data

    return mockClass
}

The service method (simplified):

void persist(MockedClass mock) {
    String string = mock.data.asJson()
    def domain = new MyDomain(data: mock.data.asJson())
    domain.save()
}

When I step into this code with the debugger I can immediately see that the string has turned from {"content":"irrelevant"} in the string variable to "{\"content\":\"irrelevant\"}" in the domain variable.

It's only logical now, that in my test a comparison of the saved domain class string does not match the mocked input.

This is how MyDomain.data data looks when it's read from the database:

"\"\\\"{\\\\\\\"content\\\\\\\":\\\\\\\"irrelevant\\\\\\\"}\\\"\""

This is the same string parsed with new JsonSlurper().parseText(MyDomain.data):

"\"{\\\"content\\\":\\\"irrelevant\\\"}\""

Here's the mocked string parsed with JsonSlurper (as above):

[content:irrelevant]

Obviously the last example is what I expect. Can anybody tell me why Groovy/Grails adds a bulk load of crappy \\ to my simple and properly escaped string? I could even try a Groovy string '{"content":"irrelevant"}' but that doesn't make the slightest difference.

Upvotes: 1

Views: 833

Answers (1)

Robert Lohr
Robert Lohr

Reputation: 635

Just by accident (on the hunt for some other weird issues that arose after renaming a package) I found out what was causing the problem. In my domain class I do not only have the String property but transient getter and setter that return a JSON object from that string or accept a JSON object and turn that into a string.

class MyDomain {
    String data
    static mapping = {
        data type: StringJsonUserType
    }

    static transients = ['dataJson']

    def getDataJson() {
        return new JsonSlurper().parseText(data)
    }

    void setDataJson(def data) {
        data = JsonOutput.toJson(data)
    }
}

Unfortunately I had a typo in setDataJson. It's name was setData and therefore it was used as the setter for my String in the service method.

void persist(MockedClass mock) {
    String string = mock.data.asJson()
    def domain = new MyDomain(data: mock.data.asJson())
    domain.save()
}

That means that JsonOutput.toJson(data) converted my JSON string to another JSON string and that's where all the additional escape characters came from.

Morale of the story: Switch to a properly compiled language that enforces the type system at compile time.

Upvotes: 1

Related Questions