dymamic Schemas and nested Maps in Avro - 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.

Related

Why is GeoDjango not returning my GeoJSON in SRID 4326?

I have a model with point data stored in srid 2953.
When I serialize this data, I assumed that GeoDjando would convert this to valid GeoJSON by converting the coordinates to SRID 4326.
Maybe I need to specificly tell it to convert this?
From what I have read I understand that CRS has been depreciated from GeoJSON, and that it is only valid in SRID 4326?
class Hpnrecord(models.Model):
...
geom = models.PointField(srid=2953, null=True)
Later in a serializer I have:
class HpnrecordSerializer(serializers.GeoFeatureModelSerializer):
class Meta:
fields = "__all__"
geo_field = "geom"
model = Hpnrecord
When I view the returned data I am getting this:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
2594598.985,
7425392.375
]
},
"properties": {
}
},
as you can see, the coordinates are being displayed as Easting and Northing (the same as what is stored in the model), and not being converted to SRID 4326. My endpoint is expecting to receive this in srid 4326.
How do I specify that I expect the serializaiton to be in SRID 4326?
As you might have noticed, SRID transformations are not done automatically. I have 2 suggestions for you:
Suggestion 1: Store the data in the desired SRID
Before you store the data, you convert it first to your desired srid of 4326. Your model would change:
class Hpnrecord(models.Model):
...
geom = models.PointField(srid=4326, null=True)
Storing the data would look like this:
from django.contrib.gis.geos import Point
...
point = Point(x, y, srid=2953)
point.transform(4326)
model_instance.geom = point
model_instance.save()
Suggestion 2: Use the serializer's to_representation()
You keep your models as they are and you convert the SRID on the fly using the serializer's to_representation() method, see the docs. Note that converting it on the fly will result in a speed penalty, but you can leave the models as they are.
class HpnrecordSerializer(serializers.GeoFeatureModelSerializer):
class Meta:
fields = "__all__"
geo_field = "geom"
model = Hpnrecord
def to_representation(self, instance):
"""Convert `geom` to srid 4326."""
ret = super().to_representation(instance)
ret['geom'].transform(4326)
return ret

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"
}

Swagger query parameter template

I have on query parameter which is little bit complex and i have my own syntax to make that value. Its has more then one variable to make one complete string value.
Let suppose name of parameter is index which has row and column like to make this value 20:30
index = { row: 20, col:30 }
index2 = { row: 20, col:30, chr: 15 }
Now i wanted to make it as
example.com?index=20:30
example.com?index2=20:30:15
Can someone tell me how can i define this in swagger ?
Thank you.
Make your swagger parameter a string and in your code behind handle the splitting into multiple variables...
I do exactly that here:
http://turoapi.azurewebsites.net/swagger/ui/index#/Echo/Echo_Get
"parameters": [
{
"name": "location",
"in": "query",
"description": "SoFL= 26.16,-80.20",
"required": true,
"type": "string"
},
That location is (Latitude,Longitude) and I split it with a C# TypeConverter
...and the request looks like:
http://turoapi.azurewebsites.net/api/Echo?location=26.16,-80.20
The code for that WebApi is here:
https://github.com/heldersepu/TuroApi

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

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).

Exclude fields from document in mongoid?

I have a Record model with many dynamic attributes. I want to make a request to the model an send the response as JSON to the client. But i want to exclude fields like _id and all foreign_keys in this model.
I found an interessting answer how to exclude the values of some keys: How do I exclude fields from an embedded document in Mongoid?, but the keys in the response still exists.
I got:
{
"_id": 1,
"name": "tom"
}
And the without method makes:
{
"_id": nil,
"name": "tom"
}
But i want:
{
"name": "tom"
}
Is it possible to remove or exclude some keys and the values from the result?
You don't want to remove fields from the mongoid document, what you want to do is remove fields from the generated json.
In your controller, do
render :json => #model.to_json(:except => :_id)
Documentation for the to_json method http://apidock.com/rails/ActiveRecord/Serialization/to_json
taken from the mongodb documentation at: http://docs.mongodb.org/manual/reference/method/db.collection.find/
Exclude Certain Fields from the Result Set
The following example selects documents that match a selection criteria and excludes a set of fields from the resulting documents:
db.products.find( { qty: { $gt: 25 } }, { _id: 0, qty: 0 } )
The query returns all the documents from the collection products where qty is greater than 25. The documents in the result set will contain all fields except the _id and qty fields, as in the following:
{ "item" : "pencil", "type" : "no.2" }
{ "item" : "bottle", "type" : "blue" }
{ "item" : "paper" }
i suppose mongoid is setting the _id attribute to nil since mongoid models have a defined set of attributes (even if they are dynamic, _id, _type etc are defined). maybe you can try it with the mongodb driver.
but i think RedXVII answer is the more practical way to go

Resources