Reputation: 635
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
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