Comment sérialiser une instance de modèle dans Django?

159

Il y a beaucoup de documentation sur la façon de sérialiser un modèle QuerySet, mais comment sérialiser simplement en JSON les champs d'une instance de modèle?

Jason Christa
la source
Bien qu'il semble que vous puissiez sérialiser un ensemble de requêtes de 1 objet, vous ne pouvez pas utiliser les classes de django.corepour ce faire. Une raison particulière de ne pas utiliser la sérialisation du jeu de requêtes?
Jack M.
1
Le sérialiseur du jeu de requêtes encapsule le résultat dans deux couches de plus que nécessaire. Vous devez donc faire data [0] .fields.name au lieu de data.name.
Jason Christa
C'est ce que je pensais. J'ai rencontré le même problème lorsque j'écrivais une interface GWT pour un backend django. On dirait que David pourrait être sur quelque chose.
Jack M.
duplication possible du sérialiseur Django pour un objet
sleepycal

Réponses:

242

Vous pouvez facilement utiliser une liste pour envelopper l'objet requis et c'est tout ce dont les sérialiseurs django ont besoin pour le sérialiser correctement, par exemple:

from django.core import serializers

# assuming obj is a model instance
serialized_obj = serializers.serialize('json', [ obj, ])
Xaralis
la source
9
Mais en réponse, vous devez indexer l'élément zéro de l'objet JSON pour accéder à l'objet sérialisé. Juste quelque chose à noter.
Davor Lucic
10
Et que diriez-vous de sérialiser tous les objets référencés avec l'objet racine?
paweloque
1
Vous ne voulez pas [0]à la fin de votre dernière ligne, comme @DavorLucic l'a suggéré? Et pas besoin de virgule de fin dans votre liste littérale (pour l'amour de PEP8;).
plaques de cuisson
La désérialisation nécessite également une étape supplémentaire; voir stackoverflow.com/a/29550004/2800876
Zags le
5
Cela n'a pas fonctionné pour moi. Django lance l'objet AttributeError 'tuple' n'a pas d'attribut '_meta'
adamF
71

Si vous avez affaire à une liste d'instances de modèle, le mieux que vous puissiez faire est d'utiliser serializers.serialize(), cela répondra parfaitement à vos besoins.

Cependant, vous devez faire face à un problème en essayant de sérialiser un seul objet, pas un listobjet. De cette façon, afin de se débarrasser des différents hacks, il suffit d'utiliser Django model_to_dict(si je ne me trompe pas, serializers.serialize()cela dépend aussi):

from django.forms.models import model_to_dict

# assuming obj is your model instance
dict_obj = model_to_dict( obj )

Vous avez maintenant juste besoin d'un json.dumpsappel direct pour le sérialiser vers json:

import json
serialized = json.dumps(dict_obj)

C'est tout! :)

Pavel Daynyak
la source
6
cela échoue avec les champs UUID, devrait être clair sinon
Alvin
2
échoue avec les datetimechamps. Résolu de cette façon json.loads(serialize('json', [obj]))[0]pendant que le sérialiseur estdjango.core.serializers.serialize
Lal Zada
43

Pour éviter le wrapper de tableau, supprimez-le avant de renvoyer la réponse:

import json
from django.core import serializers

def getObject(request, id):
    obj = MyModel.objects.get(pk=id)
    data = serializers.serialize('json', [obj,])
    struct = json.loads(data)
    data = json.dumps(struct[0])
    return HttpResponse(data, mimetype='application/json')

J'ai aussi trouvé cet article intéressant sur le sujet:

http://timsaylor.com/convert-django-model-instances-to-dictionaries

Il utilise django.forms.models.model_to_dict, qui ressemble à l'outil parfait pour le travail.

julien
la source
9
si c'est la meilleure façon de sérialiser un seul modèle dans django, alors c'est horrible car on ne devrait pas avoir besoin de désérialiser le json et de le sérialiser à nouveau.
Herbert
@Herbert, peut-être. Mais voilà. Si vous avez un meilleur moyen, je suis toute oreille. Cela ne devrait pas avoir beaucoup d'inconvénients pratiques, car la récupération et le dés / ré-encodage d'un seul objet ne devraient pas être aussi gourmands en ressources. Faites-en une fonction d'aide ou étendez / mélangez avec vos objets en tant que nouvelle méthode si vous souhaitez masquer l'horreur.
Julian
1
Cacher l'horreur n'est pas le problème et peut-être même pas cette solution; ce qui me surprend, c'est que c'est la meilleure façon de faire de django.
Herbert
14

Il y a une bonne réponse à cela et je suis surpris qu'elle n'ait pas été mentionnée. En quelques lignes, vous pouvez gérer les dates, les modèles et tout le reste.

Créez un encodeur personnalisé capable de gérer les modèles:

from django.forms import model_to_dict
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import Model

class ExtendedEncoder(DjangoJSONEncoder):

    def default(self, o):

        if isinstance(o, Model):
            return model_to_dict(o)

        return super().default(o)

Maintenant, utilisez-le lorsque vous utilisez json.dumps

json.dumps(data, cls=ExtendedEncoder)

Désormais, les modèles, les dates et tout peuvent être sérialisés et il n'est pas nécessaire que ce soit dans un tableau ou sérialisé et non sérialisé. Tout ce que vous avez personnalisé peut simplement être ajouté à la defaultméthode.

Vous pouvez même utiliser le JsonResponse natif de Django de cette façon:

from django.http import JsonResponse

JsonResponse(data, encoder=ExtendedEncoder)
``
Kagronick
la source
3
Cette solution est simple et élégante. L'encodeur peut être utilisé avec les méthodes json.dumpset json.dump. De cette façon, vous n'avez pas besoin de modifier le flux de travail de l'application car vous utilisez des objets personnalisés ou ajoutez un autre appel de méthode avant la conversion en json. Ajoutez simplement votre code de conversion dans l'encodeur et vous êtes prêt à partir.
Claudiu
11

Il semble que ce que vous demandez implique de sérialiser la structure de données d'une instance de modèle Django pour l'interopérabilité. Les autres affiches sont correctes: si vous vouliez que le formulaire sérialisé soit utilisé avec une application python qui peut interroger la base de données via l'API de Django, alors vous voudriez sérialiser un jeu de requêtes avec un objet. Si, d'un autre côté, vous avez besoin d'un moyen de regonfler l'instance de modèle ailleurs sans toucher à la base de données ou sans utiliser Django, alors vous avez un peu de travail à faire.

Voici ce que je fais:

Tout d'abord, j'utilise demjsonpour la conversion. C'est ce que j'ai trouvé en premier, mais ce n'est peut-être pas le meilleur. Ma mise en œuvre dépend de l'une de ses fonctionnalités, mais il devrait y avoir des moyens similaires avec d'autres convertisseurs.

Deuxièmement, implémentez une json_equivalentméthode sur tous les modèles dont vous pourriez avoir besoin en série. C'est une méthode magique pour demjson, mais c'est probablement quelque chose à quoi vous voudrez réfléchir, quelle que soit l'implémentation que vous choisissez. L'idée est de renvoyer un objet directement convertible en json(c'est- à -dire un tableau ou un dictionnaire). Si vous voulez vraiment faire cela automatiquement:

def json_equivalent(self):
    dictionary = {}
    for field in self._meta.get_all_field_names()
        dictionary[field] = self.__getattribute__(field)
    return dictionary

Cela ne vous sera utile que si vous avez une structure de données complètement plate (non ForeignKeys, uniquement des nombres et des chaînes dans la base de données, etc.). Sinon, vous devriez sérieusement réfléchir à la bonne façon de mettre en œuvre cette méthode.

Troisièmement, appelez demjson.JSON.encode(instance)et vous avez ce que vous voulez.

David Berger
la source
Je n'ai pas encore essayé le code mais je voulais juste signaler quelques erreurs. C'est instance._meta.get_all_field_names () et getattribute est une fonction donc devrait avoir () et non [].
Jason Christa
en plus de FK, cela ne fonctionnera pas pour les champs datetime (sauf s'il y a de la magie dans demjson.JSON.encode)
Skylar Saveland
8

Si vous demandez comment sérialiser un seul objet à partir d'un modèle et que vous savez que vous n'obtiendrez qu'un seul objet dans le jeu de requêtes (par exemple, en utilisant objects.get), utilisez quelque chose comme:

import django.core.serializers
import django.http
import models

def jsonExample(request,poll_id):
    s = django.core.serializers.serialize('json',[models.Poll.objects.get(id=poll_id)])
    # s is a string with [] around it, so strip them off
    o=s.strip("[]")
    return django.http.HttpResponse(o, mimetype="application/json")

ce qui vous donnerait quelque chose de la forme:

{"pk": 1, "model": "polls.poll", "fields": {"pub_date": "2013-06-27T02:29:38.284Z", "question": "What's up?"}}
Benlast
la source
Mais cela supprimera tous les crochets, pas seulement ceux de l'extérieur. Mieux? "o = s [1: -1]"?
Julian
5

J'ai résolu ce problème en ajoutant une méthode de sérialisation à mon modèle:

def toJSON(self):
    import simplejson
    return simplejson.dumps(dict([(attr, getattr(self, attr)) for attr in [f.name for f in self._meta.fields]]))

Voici l'équivalent détaillé de ceux qui sont opposés aux one-liners:

def toJSON(self):
    fields = []
    for field in self._meta.fields:
        fields.append(field.name)

    d = {}
    for attr in fields:
        d[attr] = getattr(self, attr)

    import simplejson
    return simplejson.dumps(d)

_meta.fields est une liste ordonnée de champs de modèle accessibles depuis les instances et depuis le modèle lui-même.

davidchambers
la source
3
Bien que l'idée puisse sembler bonne au début, il convient de souligner que l'utilisation de cette approche a des conséquences. Vous liez une sortie de sérialisation spécifique à votre modèle.
Jonas Geiregat
@JonasGeiregat puisque cette méthode est définie modèle à modèle, quel est le problème avec l'approche? Malheureusement, cela semble être le seul moyen de renvoyer un objet json contenant à la fois les champs et la clé primaire de l'instance.
dopatraman
5

Voici ma solution pour cela, qui vous permet de personnaliser facilement le JSON ainsi que d'organiser les enregistrements associés

Tout d'abord, implémentez une méthode sur le modèle. J'appelle jsonmais vous pouvez l'appeler comme vous voulez, par exemple:

class Car(Model):
    ...
    def json(self):
        return {
            'manufacturer': self.manufacturer.name,
            'model': self.model,
            'colors': [color.json for color in self.colors.all()],
        }

Ensuite, dans la vue que je fais:

data = [car.json for car in Car.objects.all()]
return HttpResponse(json.dumps(data), content_type='application/json; charset=UTF-8', status=status)
DanH
la source
Dans Python 3, il devientcar.json()
T.Coutlakis
4

Utilisez la liste, cela résoudra le problème

Étape 1:

 result=YOUR_MODELE_NAME.objects.values('PROP1','PROP2').all();

Étape 2:

 result=list(result)  #after getting data from model convert result to list

Étape 3:

 return HttpResponse(json.dumps(result), content_type = "application/json")
niran
la source
Il semble qu'il sera toujours sérialisé comme un tableau json (d'objets) et non comme un objet nu, ce que OP a demandé. iow, ce n'est pas différent de la méthode de sérialisation régulière.
Julian
1
Cela échouera avec une erreur de sérialisation JSON. Les objets Queryset ne sont pas sérialisables
dopatraman
4

Pour sérialiser et désérialiser, utilisez ce qui suit:

from django.core import serializers

serial = serializers.serialize("json", [obj])
...
# .next() pulls the first object out of the generator
# .object retrieves django object the object from the DeserializedObject
obj = next(serializers.deserialize("json", serial)).object
Zags
la source
J'obtiens "l'objet générateur n'a pas d'attribut" suivant ". Une idée?
user2880391
1
@ user2880391 J'ai mis à jour ceci pour Python 3. Est-ce que cela résout le problème?
Zags du
4

.values() est ce dont j'avais besoin pour convertir une instance de modèle en JSON.

Documentation .values ​​(): https://docs.djangoproject.com/en/3.0/ref/models/querysets/#values

Exemple d'utilisation avec un modèle appelé Project .

Remarque: j'utilise Django Rest Framework

    @csrf_exempt
    @api_view(["GET"])
    def get_project(request):
        id = request.query_params['id']
        data = Project.objects.filter(id=id).values()
        if len(data) == 0:
            return JsonResponse(status=404, data={'message': 'Project with id {} not found.'.format(id)})
        return JsonResponse(data[0])

Résultat d'un identifiant valide:

{
    "id": 47,
    "title": "Project Name",
    "description": "",
    "created_at": "2020-01-21T18:13:49.693Z",
}
Richard
la source
3

Si vous souhaitez renvoyer l' objet de modèle unique en tant que réponse json à un client, vous pouvez effectuer cette solution simple:

from django.forms.models import model_to_dict
from django.http import JsonResponse

movie = Movie.objects.get(pk=1)
return JsonResponse(model_to_dict(movie))
flowfelis
la source
2

Utilisez Django Serializer avec pythonformat,

from django.core import serializers

qs = SomeModel.objects.all()
serialized_obj = serializers.serialize('python', qs)

Quelle est la différence entre jsonet pythonformat?

Le jsonformat renverra le résultat comme stralors que pythonretournera le résultat dans l'un listou l' autreOrderedDict

JPG
la source
c'est trop malheureux
JPG
Je reçois OrderedDict, pasdict
user66081
1

Il ne semble pas que vous puissiez sérialiser une instance, vous devrez sérialiser un QuerySet d'un objet.

from django.core import serializers
from models import *

def getUser(request):
    return HttpResponse(json(Users.objects.filter(id=88)))

Je suis à court de la svnversion de django, donc cela peut ne pas être dans les versions antérieures.

Jack M.
la source
1
ville = UneVille.objects.get(nom='lihlihlihlih')
....
blablablab
.......

return HttpResponse(simplejson.dumps(ville.__dict__))

Je renvoie le dict de mon instance

donc il renvoie quelque chose comme {'champ1': valeur, "champ2": valeur, ....}


la source
cela va casser:TypeError: <django.db.models.base.ModelState object at 0x7f2b3bf62310> is not JSON serializable
Julio Marins
1

que diriez-vous de cette façon:

def ins2dic(obj):
    SubDic = obj.__dict__
    del SubDic['id']
    del SubDic['_state']
return SubDic

ou excluez tout ce que vous ne voulez pas.

Reorx
la source
0

Toutes ces réponses étaient un peu hacky par rapport à ce que j'attendrais d'un framework, la méthode la plus simple, je pense de loin, si vous utilisez le framework rest:

rep = YourSerializerClass().to_representation(your_instance)
json.dumps(rep)

Cela utilise directement le sérialiseur, en respectant les champs que vous avez définis dessus, ainsi que les associations, etc.

iantbutler
la source