I would like to create a serializer that given an input JSON object select certain attributes.
There is an legacy system that sends an object, for example:
{
"a": {
"b": "test"
}
}
I need to keep certain properties of the object. For example a.b. I have created the following serializer, but it is not working:
class CustomSerializer(serializers.Serializer):
b = serializers.CharField(source='a.b', required=True)
Does anyone know what is failing?
Thanks!
You need to implement nested serializer. For your example, you can try something like this :
class BSerializer(serializers.Serializer):
b = serializers.CharField()
class ASerializer(serializers.Serializer):
a = BSerializer()
Note : this is just an example of nested serializer. You might need to add few things to make it work properly for you (adding Meta, other fields if needed, use it in view etc. )
Related
I'm trying to deserialize a complex GET request into a structure of nested objects.
The GET requests looks like:
curl 'localhost:8080/?id=1&inner.id=1'
and the code should look like this:
class RootObj(val id: Int, inner: InnerObject)
class InnerObject(val id: Int)
#RestController
class SearchController {
#GetMapping(path = ["/"])
fun getRoot(rootObj: RootObj): String {
return "ok"
}
}
This doesn't work out of the box I guess because spring doesn't know how properly create this nested structure.
Parameter specified as non-null is null: [...] parameter inner","path":"/"}%
Is there a way to overcome this problem? Maybe providing a totally custom deserializer code?
As alternative solution, I guess I could flatten the object hierarchy but for doing so I must be able to map a query parameter like inner.id to a field named innerId
Spring can actually map the query params to the custom object directly, but you need to provide defaults to the params of the custom object constructor.
So you need to define your classes as below for it to work
class RootObj(val id: Int = 0, val inner: InnerObject = InnerObject(0))
class InnerObject(var id: Int = 0)
Do note that the id field of InnerObject would have to be declared as var for Spring to be able to map it. Then curl 'localhost:8080/?id=1&inner.id=1' would work fine.
I am using a model that consists of many fields. There is one field that is a property, and it returns an instance of a model. Something like the following:
class A(Model):
#property
def last_obj(self):
# Returns an object
The issue I'm having is that this property can return 2 different Model types. It can either return an object of type one, or an object of type two. This creates complications in the serializer. I have a serializer that consists of nested serializers. The two objects are similar enough that one serializer can be used over the other, but then the fields unique to them are not serialized.
class A_Serializer(Serializer):
class SerializerOne(CustomSerializer):
#Serializes certain fields in custom manner
class Meta:
model = models.one
exclude = ('id')
base_name = 'one'
class SerializerTwo(CustomSerializer):
#Serializes certain fields in custom manner
class Meta:
model = models.two
exclude = ('id')
base_name = 'two'
last_obj = SerializerOne() #This works, but not viable because of what I stated above
So my solution to be able to dynamically call the correct serializer, was to conditionally serialize the property within a serializer method field:
class A_Serializer(Serializer):
class SerializerOne(CustomSerializer):
#Serializes certain fields in custom manner
class Meta:
model = models.one
exclude = ('id')
base_name = 'one'
class SerializerTwo(CustomSerializer):
#Serializes certain fields in custom manner
class Meta:
model = models.two
exclude = ('id')
base_name = 'two'
def get_last_obj(self, instance):
if (isinstance(instance.last_obj, models.one)):
return self.SerializerOne(instance.last_obj).data
else:
return self.SerializerTwo(instance.last_obj).data
last_obj = SerializerMethodField() #Does not work
However, this solution creates the error "NoneType Object is not iterable" and it happens at
super(ReturnDict, self).__init__(*args, **kwargs) in rest_framework/utils/serializers_helpers.py in init which causes the error at return ReturnDict(ret, serializer=self) in rest_framework/serializers.py in data
I do not understand why calling a nested serializer like obj = Serializer() works, but calling the serializer explicitly like obj = Serializer(instance).data does not work in this situation. Can anyone figure out what I have been doing wrong? Thank you.
I have found out from here that when working with hyperlinked relations (which in my case was the CustomSerializer that SerializerOne and SerializerTwo were inheriting from), you must pass the request object through context. The reason why obj = Serializer() works, but obj = Serializer(instance).data does not work is that in the former, the request object is automatically added through context through DRF. While in the latter, it is being explicitly called so you must pass context with the request object manually. So for me to get it working, I did:
return self.SerializerOne(instance.last_obj, context={'request': self.context['request']}).data
inside the serializer method field.
We can use serializer as a field inside another serializer..
Wonder why there's a Field class and Serializer class in DRF?
class CommentSerializer(serializers.Serializer):
user = UserSerializer()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
example serializer is taken from the doc https://www.django-rest-framework.org/api-guide/serializers/
As you can see, UserSerializer is much like a Field .
I'm just curious why they have serializer and field class separately..
Serilaizer is infact a field in DRF. Serializers can be nested and that is why it can be used as a field in other serializers. And yes, if you check the source code, the BaseSerializer is a subclass of Field as the serializer is just a special case of a field.
In my opinion:
In django rest framwork, you can think Serializer like a mask. It cover your origin data and change it to anything you want. Like format your json data , or validate your input data have correct format or not.
In your example,
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
Comment have 2 direct field type CharField and DateTimeField.
user = UserSerializer()
Comment have field type is UserSerializer. This is different Serializer, and django know your CommentSerializer will have relationship with UserSerializer. And anything define in UserSerializer will use in here, for format json output or validate. And with define this nested objects, your output json will have more data like
'user': {'email': 'foobar', 'username': 'doe'}
And if you want create Comment with one user, you must pass all validate define in UserSerializer.
Conclude: in this example
Field class use for direct field.
Serializer class for relationship with other class
I'm trying retrofit 2 for the first time and I have no idea how to tell it to get "Category" objects from an jsonarray named "data".
Method 1
If I do it like this it fails:
#GET("category")
Call<List<Category>> listCategories();
Method 2
But when I make a new model, called "Categories", which holds a List and is annotated with #SerializedName("data"), it works flawlessly.
#GET("category")
Call<Categories> listCategories();
My Question
Should I annotate something in the interface, like this
#GET("category")
#Annotation to look inside "data"
Call<List<Category>> listCategories();
Or should I annotate my "Category" model to tell Retrofit (or GSON)
that it lives inside the json array "data"?
JSON
{"data":[{"id":1,"name":"Fist Name","parent":0},{"id":2,"name":"Second Name","parent":1}]}
Method 2 Is correct and we use it when we dont want to use/define the json response object/arrays key names(field names). instead provide our own. Eg. In below code List object name is items but while Serialization and Deserialization it uses, what you have defined in #SerializedName annotation that is data.
public class Categories {
//solution 1
List<Category> data;//object name must match with the json response
//solution 2
#SerializedName("data")
List<Category> items;
}
Should I annotate something in the interface
No. There is no such annotation available and everything you can do is only in Response type class.
I am looking for a way to create a nested serializer which will behave differently when writing or reading. In my specific case, I have some models that look like this:
class Frame(models.Model):
frame_stack = models.IntegerField()
frame_reach = models.IntegerField()
# ...
class CustomBicycle(models.Model):
frame = models.ForeignKey(Frame)
# ...
When the CustomBicycle model is serialized, I want it to look like this:
{
"id": 1,
"frame": {
"id": 1,
"frame_stack": 123,
"frame_reach": 234
}
}
Let's say I want to submit some JSON to be de-serialized in order to update the above CustomBicycle instance. In that case, I'd like to submit the following JSON in order to update its Frame to the one with id = 2:
{
"id": 1,
"frame": {
"id": 2,
"frame_stack": 345,
"frame_reach": 456
}
}
The serializer I need would look at the "frame.id" property in the JSON and then update the CustomBicycle instance to point to Frame #2. It would ignore all other properties under the "frame" key in the JSON (but it should allow those properties to be there).
The reason I want this is that I have something analogous to model classes in the Javascript of my application. I can easily serialize these Javascript model instances into JSON that looks like that which is above. I don't want to have to program in extra logic to serialize the model data in the ways that would be necessary to work with a flat JSON API, as is required by the current version of Django Rest Framework.
Any ideas?
Here's my own kinda hacky solution to this one:
class NestedForeignKeyAssignmentMixin(object):
def field_from_native(self, data, files, field_name, into):
if self.read_only:
return
value = data.get(field_name)
id = value.get('id') if value else None
if not id:
if self.required:
raise ValidationError(self.error_messages['required'])
into[field_name] = None
return
model = self.Meta.model
try:
into[field_name] = model._default_manager.get(id=id)
except model.DoesNotExist:
raise ValidationError('{name} with id `{id}` does not exist'.format(
name=model.__name__,
id=id,
))
class FrameSerializer(NestedForeignKeyAssignmentMixin, serializers.ModelSerializer):
# ...
class Meta:
model = Frame
class CustomBicycleSerializer(serializers.ModelSerializer):
frame = FrameSerializer()
# ...