Retrieving data using Grails Domain returns with a Class Key by default - grails

I'm working with a Grails query service and I'm using these the code blocks to retrieve database rows via a domain class.
adjustmentCodeList = AdjustmentCode.findAll {
or {
ilike('description', "%$filterText%")
like('id', "%$filterText%")
}
}
adjustmentCodeList = AdjustmentCode.list()
adjustmentCodeList = AdjustmentCode.list(max: count, offset: from)
It works fine actually, but there is a little problem though. It returns the following list (some sensitive data are omitted):
[
{
"class": "rvms.maintenance.AdjustmentCode",
"id": ...,
"description": ...,
"lastUpdateBy": ...,
"lastUpdateDate": ...,
"status": ...,
"statusDate": ...,
"type": ...
},
{
"class": "rvms.maintenance.AdjustmentCode",
"id": ...,
"description": ...,
"lastUpdateBy": ...,
"lastUpdateDate": ...,
"status": ...,
"statusDate": ...,
"type": ...
},
...
{
"class": "rvms.maintenance.AdjustmentCode",
"id": ...,
"description": ...,
"lastUpdateBy": ...,
"lastUpdateDate": ...,
"status": ...,
"statusDate": ...,
"type": ...
}
]
It includes the domain class name. How can I remove the class key using some config? My current solution is to manually remove the class key from the list by iterating it inside a loop, removing that key one at a time. But maybe... there is another Grails-ly way.
If you want to see the domain, it looks like this:
package rvms.maintenance
import grails.util.Holders
import groovy.sql.Sql
import oracle.jdbc.OracleTypes
import java.sql.Connection
class AdjustmentCode implements Serializable {
String id
String description
String type
String status
Date statusDate
String lastUpdateBy
Date lastUpdateDate
static mapping = {
table '...'
version false
id column : '...'
description column : '...'
type column : '...'
status column : '...'
statusDate column : '...'
lastUpdateBy column : '...'
lastUpdateDate column : '...'
}
Map getAdjustmentCodeValues() {
Map values = [];
values << [id: this.getId()]
values << [description: this.getDescription()]
values << [type: this.getType()]
values << [status: this.getStatus()]
values << [statusDate: this.getStatusDate()]
values << [lastUpdateBy: this.getLastUpdateBy()]
values << [lastUpdateDate: this.getLastUpdateDate()]
return values
}
}

The Grails way to accomplish this is to customize the marshaller. I've explained how to do this with named marshallers in this answer and the same concept applies to your case as well (minus the named portion).

Related

How to indicate that the response body is a List in Swagger UI docs using FastAPI?

I describe the structure of the outgoing JSON in the model class, however I cannot make the output as a list.
My model class:
class versions_info(BaseModel):
""" List of versions """
version : str = Field(..., title="Version",example="2.1.1")
url : str = Field(..., title="Url",example="https://ocpi.wedwe.ww/ocpi/2.1.1/")
And in the documentation I see:
However, I need it to be displayed as:
[
{
"version": "2.1.1",
"url": "https://www.server.com/ocpi/2.1.1/"
},
{
"version": "2.2",
"url": "https://www.server.com/ocpi/2.2/"
}
]
What am I doing wrong?
You can indicate that you're returning a List by wrapping the response_model you've defined in List[<model>].
So in your case it'd be:
#app.get('/foo', response_model=List[versions_info])

openapi, list of strings as query parameter

I'm defining a query parameter, with openapi 3.0.1, as follows
{
"name" : "sort",
"in" : "query",
"description" : "Sorting criteria. Example: productCode,desc",
"required" : false,
"explode" : false,
"schema" : {
"type" : "array",
"items" : {
"type" : "string"
}
}
}
On swagger-ui 3.51.1 if I add two strings
"parameter1,asc"
"parameter2,desc"
they are serialized correctly (as a list of strings with 2 elements), but if I add only one string
"parameter1,asc"
it will get serialized incorrectly as a list of strings with 2 elements (parameter1 and asc).
I do not understand why the string is exploded! Any help is greatly appreciated.
In your example, the query parameter has no style defined, so it defaults to style: form. Non-exploded form style treats the comma , as a separator of array items. This results in ambiguity because the values of your array items also use commas as an inner separator.
Possible solutions involve changing your backend code and/or the OpenAPI parameter definition.
Adjust the backend code so that it splits the received sort string on every second comma rather than every comma.
Or, use another serialization method for the sort array, for example:
explode: true to send the exploded array:
?sort=parameter1,asc&sort=parameter2,desc
style: pipeDelimited + explode: false to separate array items using | instead of commas:
?sort=parameter1,asc|parameter2,desc
Or, change sort to be an object/map instead of an array:
{
"name": "sort",
"in": "query",
"description": "Sorting criteria. Example: productCode,desc",
"required": false,
"explode": false,
"schema": {
"type": "object",
"additionalProperties": {
"type": "string",
"enum": ["asc", "desc"]
}
}
}
In this case, your current query string format
?sort=parameter1,asc,parameter2,desc
unambiguously corresponds to:
{
"parameter1": "asc",
"parameter2": "desc"
}

Returning Neo4j map projection with WebFlux

I have the nodes user and game with some relationships between them.
My REST API should return all relationships between the games and 1 user.
The cypher query i use is:
MATCH (u:User {id: '1234'} ) -[rel]- (game:Game) return game{.*, relationships: collect(DISTINCT rel)}
In my Neo4j Browser, everything works as expected and i see all properties i need.
But the GetMapping retuns everything except the relationship properties.
Neo4j Browser
{
"relationships": [
{
"identity": 54,
"start": 9,
"end": 8,
"type": "OWNED",
"properties": {
"ownedDate": "2021-07-03"
}
},
{
"identity": 45,
"start": 9,
"end": 8,
"type": "PLAYED",
"properties": {
"times": 5
}
}
],
"name": "Blood Rage",
"state": "ACTIVE",
"id": "1c152c91-4044-41f0-9208-0c436d6f6480",
"gameUrl": "https://asmodee.de/blood-rage"
}
GetMapping result (As you can see, the relationships are empty, but i have more empty JsonObjects, when there are more Relationships
{
"game": {
"relationships": [
{},
{}
],
"name": "Blood Rage",
"gameUrl": "https://asmodee.de/blood-rage",
"state": "ACTIVE",
"id": "1c152c91-4044-41f0-9208-0c436d6f6480"
}
}
The GetMapping is:
...
final ReactiveNeo4jClient client;
...
...
...
#GetMapping(value = { "/{id}/games"})
#RolesAllowed({"user", "admin"})
Flux<Map<String, Object>> findGamesByUser(#PathVariable String id){
String query = "MATCH (uuser:User {id: '" + id + "'} ) -[rel]- (game:Game) return game{.*, relationships: collect(DISTINCT rel)}";
return client.query(query).fetch().all();
}
A RelationshipProperty-Example
#RelationshipProperties
#Data
#Builder
public class PlayedGame {
#Id
#GeneratedValue
private Long relationshipId;
#Property
int times = 0;
#TargetNode private GameEntity game;
public int addPlay(){
this.times = this.times + 1;
return this.times;
}
}
What do i have to change in my GetMapping to show the relationship-properties?
Thank you,
Kevin
You need to return the actual nodes and relationships, otherwise you're missing the id-mapping.
There should be examples in the SDN docs.
Best if you have a small reproducible example (e.g. with the default movies graph).
Not sure if there is something off in your SDN setup, in general for such simple queries you should be able to just use a repository and not need to write cypher queries by hand.
The general information given by Michael is correct but there is more in you question:
First of all the meta domain model is completely ignored if you are using the Neo4jClient. It does not automatically map anything back but uses the driver's types.
As a result you will end up with an (current state of this answer) InternalRelationship which does not have any getter-methods.
I assume that you are serializing the result in the application with Jackson. This is the reason why you see objects that represent the relationships but without any content within.
If you want to get things mapped for you, create also the domain objects properly and use (at least) the Neo4jTemplate with your query.
If you model User, Game, and the relationship properties like PlayedGame correctly, a
neo4jTemplate.findAll("MATCH (u:User)<-[rel]-(g:Game) return u, collect(rel), collect(g)", User.class)
will map the results properly. Also if this is all you have, you could also skip the custom query at all and use
neo4jTemplate.findAll(User.class)
or
neo4jTemplate.findById(useId, User.class)

dymamic Schemas and nested Maps in Avro

I'm new to Avro, and am trying to write some code to serialize some nested objects.
The structure of the objects looks like this:
class Parcel {
String recipe;
Map<Integer, PluginDump> dumps;
}
class PluginDump {
byte[] state;
Map<String, Param> params;
}
class Param {
Type type; //can be e.g. StringType, BooleanType, etc
Object value;
}
So I can't use a static avro schema - each PluginDump will have a different schema depending on the types within it.
I have written some code which can generate a Schema based on an individual PluginDump.
So when serializing a Parcel, how do I 'put' each PluginDump entry?
Here is my code:
Schema parcelSchema = AvroHelper.getSchema(p);
GenericRecord parcelRecord = new GenericData.Record(parcelSchema);
parcelRecord.put("recipe", p.getRecipe().toJson());
for (Map.Entry<Integer, PluginDump> entry : p.getDumps().entrySet()) {
PluginDump dump = entry.getValue();
Integer uid = entry.getKey();
Schema dumpSchema = AvroHelper.getSchema(dump);//will be different for each PluginDump
parcelRecord.put(????
Any ideas?
I have a feeling my approach is wrong, but I can't find any examples in the documentation of dynamic schema generation or nested maps.
1 When you get GenericRecord parcelRecord = new GenericData.Record(parcelSchema); you have two fields in your record: recipe and dumps, so you can't iterate through the dumps, you must put prepared map with dumps in the second field of record, just like you did it for recipe: parcelRecord.put("dumps", dumps);. But in this case, you'll get ClassCastException, because PluginDump cannot be cast to org.apache.avro.generic.IndexedRecord, so you need to put in parcelRecord a Map of GenericRecords. Also you need this for Map<String, Param> params, cause Param cannot be cast to IndexedRecord too.
2 Then, I think that its better to use Lists instead of Maps, cause avro not very good enough to work with Maps with different types of keys and values.
3 About the Param class: if you will use auto-generated schema, Param class will be presented like this.
"type": "record",
"name": "Param",
"fields": [
{
"name": "type",
"type": {
"type": "record",
"name": "Type",
"namespace": "java.lang.reflect",
"fields": []
}
},
{
"name": "value",
"type": {
"type": "record",
"name": "Object",
"namespace": "java.lang",
"fields": []
}
}
]
As far as avro uses java.lang.reflect, you will lose type field after deserialization, avro will not know what type it was.
If you want to generate avro-schema manually for each Param, considering its type, you can do something like this (I used ClassUtils.getClass from apache commons-lang3, cause standart Class.forName method doesn't always work properly):
public Schema getParamSchema() throws ClassNotFoundException {
List<Schema.Field> fields = new ArrayList<>();
fields.add(new Schema.Field("key", Schema.create(Schema.Type.STRING), "Doc: key field", (Object) null));
Schema.Field f = new Schema.Field("type", ReflectData.get().getSchema(ClassUtils.getClass(((Class) this.type).getName())), "Doc: type field", (Object) null);
f.addProp("java-class", ((Class) this.type).getName());
fields.add(f);
fields.add(new Schema.Field("value", ReflectData.get().getSchema(value.getClass()), "Doc: value field", (Object) null));
return Schema.createRecord(((Class) this.type).getName() + "Param", "Doc: param record", this.getClass().getPackage().getName(), false, fields);
}
But in this case, avro will throw ClassCastException, because it can't cast Class to Boolean, Integer etc. I always had a lot of problems working with avro and java Types and Classes.
So the best advice i think will be to change you model (Parcel, PluginDump and Param i mean) to have less problems with avro. For example you can store type name like a string, and get a Type with reflection after deserializing.

How to read column value using grails excel import plugin?

I am using Grails excel import plugin to import an excel file.
static Map propertyConfigurationMap = [
name:([expectedType: ExcelImportService.PROPERTY_TYPE_STRING, defaultValue:null]),
age:([expectedType: ExcelImportService.PROPERTY_TYPE_INT, defaultValue:0])]
static Map CONFIG_BOOK_COLUMN_MAP = [
sheet:'Sheet1',
startRow: 1,
columnMap: [
//Col, Map-Key
'A':'name',
'B':'age',
]
]
I am able to retrieve the array list by using the code snippet:
def usersList = excelImportService.columns(workbook, CONFIG_USER_COLUMN_MAP)
which results in
[[name: Mark, age: 25], [name: Jhon, age: 46], [name: Anil, age: 62], [name: Steve, age: 32]]
And also I'm able to read each record say [name: Mark, age: 25] by using usersList.get(0)
How do I read the each column value?
I know I can read something like this
String[] row = usersList.get(0)
for (String s : row)
println s
I wonder is there any thing that plugin supports so that I can read column value directly rather manipulating it to get the desired result.
Your usersList is basically a List<Map<String, Object>> (list of maps). You can read a column using the name you gave it in the config. In your example, you named column A name and column B age. So using your iteration example as a basis, you can read each column like this:
Map row = usersList.get(0)
for(Map.Entry entry : row) {
println entry.value
}
Groovy makes this easier to do with Object.each(Closure):
row.each { key, value ->
println value
}
If you want to read a specific column value, here are a few ways to do it:
println row.name // One
println row['name'] // Two
println row.getAt('name') // Three
Hint: These all end up calling row.getAt('name')

Resources