Grails: setting transient fields in the map constructor - json

I'm trying to persist Maps of properties as single JSON-encoded columns, as shown in this question.
The problem I'm having is that apparently transient properties cannot be set in the default map constructor. Given any transient field:
class Test {
//...
String foo
static transients = ['foo']
}
It seems that the map constructor (which Grails overrides in various ways) simply discards transient fields:
groovy:000> t = new Test(foo:'bar')
===> Test : (unsaved)
groovy:000> t.foo
===> null
While direct assignment (through the setter method) works as expected:
groovy:000> c.foo = 'bar'
===> bar
groovy:000> c.foo
===> bar
Is there a way to make the map constructor accept transient fields?
Or rather: is there a better way to persist a Map as a single JSON-encoded DB field, rather than the method shown in the linked question?
Here's the complete example:
import grails.converters.JSON
class JsonMap {
Map data
String dataAsJSON
static transients = ['data']
def afterLoad() { data = JSON.parse(dataAsJSON) }
def beforeValidate() { dataAsJSON = data as JSON }
}
I can set data using the setter (which will then be converted into dataAsJSON) but not using the map constructor.

The map constructor in GORM uses the data binding mechanism, and transient properties are not data-bindable by default. But you can override this using the bindable constraint
class Test {
//...
String foo
static transients = ['foo']
static constraints = {
foo bindable:true
}
}

I've also replied to your original question, that you don't need json conversion to achieve what you need. However, If you need json conversion badly, why don't you implement it in your getters/setters?
class Test {
String propsAsJson
static transients = ['props']
public Map getProps() {
return JSON.parse(propsAsJson)
}
public void setProps(Map props) {
propsAsJson = props as JSON
}
}
//So you can do
Test t = new Test(props : ["foo" : "bar"])
t.save()
In this way you encapsulate the conversion stuff, and in DB you have your properties as Json.

You can simplify your case by adding the JSON-conversion methods to your domain class, they should have nothing to do with GORMing:
class Test {
String title
void titleFromJSON( json ){
title = json.toStringOfSomeKind()
}
def titleAsJSON(){
new JSON( title )
}
}

Related

How does Grails 2.5.6 parse and map request JSON to POGO?

Tl;dr: I want to get test MyCmdTest."data bind works" in this code green.
Thanks to Jeff Scott Brown for getting me that far.
I have a POGO with some custom conversions from JSON which I expect to receive in a Grails controller:
def myAction(MyCmd myData) {
...
}
With:
#Validateable
class MyCmd {
SomeType some
void setSome(Object value) {
this.some = customMap(value)
}
}
Note how customMap creates an instance of SomeType from a JSON value (say, a String). Let's assume the default setter won't work; for instance, an pattern we have around more than once is an enum like this:
enum SomeType {
Foo(17, "foos"),
Bar(19, "barista")
int id
String jsonName
SomeType(id, jsonName) {
this.id = id
this.jsonName = jsonName
}
}
Here, customMap would take an integer or string, and return the matching case (or null, if none fits).
Now, I have a unit test of the following form:
class RegistrationCmdTest extends Specification {
String validData // hard-coded, conforms to JSON schema
void test() {
MyCmd cmd = new MyCmd(JSON.parse(validData))
// check members: success
MyCmd cmd2 = JSON.parse(validData) as MyCmd
// check members: success
}
}
Apparently, setSome is called in both variants.
I also have a controller unit test that sets the request JSON to the same string:
void "register successfully"() {
given:
ResonseCmd = someMock()
when:
controller.request.method = 'POST'
controller.request.contentType = "application/json"
controller.request.json = validData
controller.myAction()
then:
noExceptionThrown()
// successful validations: service called, etc.
}
Basically the same thing also runs as integration test.
However, the mapping fails when running the full application; some == null.
Which methods do I have to implement or override so Grails calls my conversions (here, customMap) instead of inserting null where it doesn't know what to do?
It's possible to customize data binding using the #BindUsing annotation:
#BindUsing({ newCmd, jsonMap ->
customMap(jsonMap['someType'])
})
SomeType someType
See also the MWE repo.
Sources: Hubert Klein Ikkink # DZone, Official Docs (there are other ways to customize)

Spring MVC Test, MockMVC: Conveniently convert objects to/from JSON

I am used to JAX-RS and would like to have similar comfort when sending requests using Spring MVC and working with the responses, i.e. on the client side inside my tests.
On the server (controller) side I'm quite happy with the automatic conversion, i.e. it suffices to just return an object instance and have JSON in the resulting HTTP response sent to the client.
Could you tell me how to work around the manual process of converting objectInstance to jsonString or vice versa in these snippets? If possible, I'd also like to skip configuring the content type manually.
String jsonStringRequest = objectMapper.writeValueAsString(objectInstance);
ResultActions resultActions = mockMvc.perform(post(PATH)
.contentType(MediaType.APPLICATION_JSON)
.content(jsonStringRequest)
)
String jsonStringResponse = resultActions.andReturn().getResponse().getContentAsString();
Some objectInstanceResponse = objectMapper.readValue(jsonStringResponse, Some.class);
For comparison, with JAX-RS client API I can easily send an object using request.post(Entity.entity(objectInstance, MediaType.APPLICATION_JSON_TYPE) and read the response using response.readEntity(Some.class);
if you have lot's of response objects, you could create some generic JsonToObject mapper-factory. It could be then used to detect the object type from a generic response (all response objects inherit from the same generic class) and respond/log properly from a bad mapping attempt.
I do not have a code example at hand, but as a pseudocode:
public abstract GenericResponse {
public String responseClassName = null;
// get/set
}
In the server code, add the name of the actual response object to this class.
The JsonToObject factory
public ConverterFactory<T> {
private T objectType;
public ConverterFactory(T type) {
objectType = type;
}
public T convert(String jsonString) {
// Type check
GenericResponse genResp = mapper.readValue(result.getResponse().getContentAsString(),
GenericResponse.class);
if (objectType.getClass().getSimpleName().equals(genResp.getResponseClassName())) {
// ObjectMapper code
return mapper.readValue(result.getResponse().getContentAsString(),
objectType.class);
} else {
// Error handling
}
}
}
I think this could be extended to be used with annotation to do more automation magic with the response. (start checking with BeanPostProcessor)
#Component
public class AnnotationWorker implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(final Object bean, String name) throws BeansException {
ReflectionUtils.doWithFields(bean.getClass(), field -> {
// make the field accessible if defined private
ReflectionUtils.makeAccessible(field);
if (field.getAnnotation(MyAnnotation.class) != null) {
field.set(bean, log);
}
});
return bean;
}
}
The above code snippet is copied from my current project and it injects to fields, you need to change it so, that it works for methods, eg ... where you may need it.
Having this all implemented may be tricky and can't say it necessarily works even, but it's something to try if you don't mind a bit of educative work.

Mock object queries

Is it possible to create a mock object with constructor arguments. For e.g
Say I have an object and uses two kinds of constructors. How ?
Class test{
List<String> list
public test()
{
list = new ArrayList<String>()
}
public test(List<String> list)
{
this.list = list
}
}
Question 2:
Can I use expect on a real object if one of its methods returns a mock object
For e.g PreferenceService prefServ = easyMock.create(...) Now prefServ is a mock object which is returned by one of the methods in class 'Test' E.g. PreferenceService getPreferenceService(). If I create a real object of type Test can i use expect(test.getPreferenceService()).andReturn(mockPreferenceService) ??? I get an error that says incompatible return type.
I think what you want is partial mocking. You could do:
PreferenceService prefServ = createMock(PreferenceService.class);
Test defaultTest = createMockBuilder(Test.class).addMockMethod("getPreferenceService").
createMock();
expect(defaultTest.getPreferenceService()).andReturn(prefServ);
Now you have defaultTest, instantiated with the default constructor, which is a real instance of Test except that the method getPreferenceService() is mocked.
List<String> testList = new ArrayList<String>();
Test otherConstructorTest = createMockBuilder(Test.class).
addMockMethod("getPreferenceService").withConstructor(testList);
expect(defaultTest.getPreferenceService()).andReturn(prefServ);
Now you have the same as above, but this time the Test object was constructed with the List constructor.

Jackson JSON to Java mapping for same attrubute with different data type

I have a JSON object which I don't have control of and want to map it to a Java object which is pre-created.
There is one attribute in the JSON object which can be a URL or it could be a JSONArray.
Class SomeClass {
private URL items;
public URL getURL() {
return items;
}
public void setURL(URL url) {
this.items = url;
}
}
Below is the JSON:
Case A:
{
...
items: http://someurl.abc.com/linktoitems,
...
}
OR
Case B
{
...
items: [
{ "id": id1, "name": name1 },
{ "id": id2, "name": name2 }
]
...
}
If i create the POJO to map for Case A, Case B fails and vice versa. In short, is there a way to map the JSON attribute to the POJO field with different data types? In that case I will create two separate fields in the POJO named,
private URL itemLink;
private Item[] itemList;
It depends on exact details, but if what you are asking is if it is possible to map either JSON String or JSON array into a Java property, yes this can be done.
Obvious way would be to define a custom deserializer which handles both kinds of JSON input.
But it is also possible to define Java type in such a way that it can be constructed both by setting properties (which works from JSON Object) and have a single-String-arg constructor or static single-String-arg factory method marked with #JsonCreator.
Yet another possibility is to use an intermediate type that can deserialized from any JSON: both java.lang.Object and JsonNode ("JSON tree") instances can be created from any JSON. From this value you would need to do manual conversion; most likely in setter, like so:
public void setItems(JsonNode treeRoot) { .... }
What will not work, however, is defining two properties with the same name.
One thing I don't quite follow is how you would convert from List to URL though. So maybe you actually do need two separate internal fields; and setter would just assign to one of those (and getter would return value of just one).

Prevent certain fields from being serialized

In the Play framework i have a few models that have fields which are object references to other models. When i use renderJSON, i don't want those object references to be included. Currently for my needs i create a separate view model class which contains the fields i want, and in the controller i create instances of this view class as needed. Ideally i would like to be able to use the model class itself without having to write the view class.
Is there a way to annotate a field so that it will not be serialized when using renderJSON?
because play uses Gson for its Json serialization you can try the following:
public static void test()
{
Object foo = new SomeObject("testData");
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
.create();
renderJSON(gson.toJson(foo));
}
now each field marked as transient will not be serialized. There is also another (better) way. You can use the com.google.gson.annotations.Expose annotation to mark each field you want to serialize.
public static void test()
{
Object foo = new SomeObject("testData");
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
renderJSON(gson.toJson(foo));
}
Using FlexJSON with play is another option, explained in this article: http://www.lunatech-research.com/archives/2011/04/20/play-framework-better-json-serialization-flexjson
Not sure why no one has written the most direct solution to this answer so I will do it here:
Simply mark the fields you do not want serialized via Gson as transient.
Here's an example:
public class Animal
{
private String name = "dog";
transient private int port = 80;
private String species = "canine";
transient private String password = "NoOneShouldSeeThis";
}
None of the items which are marked transient will be serialized.
When deserialized they will be set to their default (class default) values.
Resulting JSON will look like the following:
{"name":"dog","species":"canine"}
For more information on transient you can see the SO
Why does Java have transient fields?
I would override renderJSON to check a the field name against a member array of serialization exclusions.