web-dev-qa-db-fra.com

Utilisez le nom de classe comme clé racine pour la sérialisation JSON Jackson

Supposons que j'ai un pojo:

import org.codehaus.jackson.map.*;

public class MyPojo {
    int id;
    public int getId()
    { return this.id; }

    public void setId(int id)
    { this.id = id; }

    public static void main(String[] args) throws Exception {
        MyPojo mp = new MyPojo();
        mp.setId(4);
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
        System.out.println(mapper.getSerializationConfig().isEnabled(SerializationConfig.Feature.WRAP_ROOT_VALUE));
        System.out.println(mapper.writeValueAsString(mp));
    }
}

Lorsque je sérialise à l'aide de Jackson ObjectMapper, je reçois juste 

true
{"id":4}

mais je veux 

true
{"MyPojo":{"id":4}}

J'ai cherché partout, la documentation de Jacksons est vraiment non organisée et pour la plupart obsolète. 

26
DanInDC

En ajoutant l'annotation jackson @JsonTypeInfo au niveau de la classe, vous pouvez obtenir le résultat attendu. Je viens d'ajouter aucun changement dans votre classe.

package com.test.jackson;

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;

@JsonTypeInfo(include=As.WRAPPER_OBJECT, use=Id.NAME)
public class MyPojo {
    // Remain same as you have
}

sortie:

{
    "MyPojo": {
        "id": 4
    }
}
28
Arun Prakash

Je n'utilise pas Jackson, mais en cherchant, j'ai trouvé cette configuration qui semble être ce que vous voulez: WRAP_ROOT_VALUE

Fonction qui peut être activée pour créer une valeur racine (généralement un objet JSON mais n'importe quel type) encapsulée dans un objet JSON à propriété unique, où clé est désignée comme "nom de racine", déterminée par l'introspecteur d'annotation (notamment pour JAXB qui utilise @XmlRootElement .name) ou fallback (nom de classe non qualifié). Cette fonctionnalité est principalement destinée à la compatibilité JAXB.

Le paramètre par défaut est false, signifiant racine la valeur n'est pas emballée.

Pour que vous puissiez configurer le mappeur:

objectMapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);

J'espère que ça t'aide...

25
Aito

Voici un moyen d'y parvenir

Map<String, MyPojo> singletonMap = Collections.singletonMap("mypojo", mp);
System.out.println(mapper.writeValueAsString(singletonMap));

Sortie {"Mypojo": {"id": 4}}

Ici, l’avantage est que nous pouvons donner notre nom à la clé racine de l’objet json. Par le code ci-dessus, mypojo sera la clé racine. Cette approche sera plus utile lorsque nous utilisons un modèle de script Java tel que Moustache.js pour l'itération des objets json 

12
Anish George

Pour ce faire, vous devez utiliser l'annotation JsonTypeInfo sur votre classe et en particulier WRAPPER_OBJECT

@JsonTypeName("foo")                                                                                         
@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT ,use = JsonTypeInfo.Id.NAME)

public class Bar(){
)
4
user2083529

Il y a aussi une annotation Nice pour cela:

@JsonRootName(value = "my_pojo")
public class MyPojo{
  ...
}

va générer:

{
  "my_pojo" : {...}
}
3
JeanValjean

il y a une autre manière que j'ai utilisée et qui a fonctionné pour moi ... Je travaille avec une jarre tierce partie, je n'ai donc aucun contrôle sur les annotations ... Je dois donc écrire à travers un peu de hack.

Remplacement: org.codehaus.jackson.map.ser.BeanSerializerFactory.findBeanProperties(SerializationConfig, BasicBeanDescription)

Ajoutez votre propriété comme ci-dessous

List<BeanPropertyWriter> props = super.findBeanProperties(config, beanDesc);
BeanPropertyWriter bpw = null;
try {
     Class cc = beanDesc.getType().getRawClass();
     Method m = cc.getMethod("getClass", null);
     bpw = new BeanPropertyWriter("$className", null, null, m, null,true, null);
} catch (SecurityException e) {
  // TODO
} catch (NoSuchMethodException e) {
  // TODO
}
props.add(bpw);
return props;

De cette façon, j'ai plus de contrôle et je peux faire d'autres types de filtres.

1
shailendra

Que diriez-vous de la solution la plus simple possible? utilisez simplement une classe wrapper comme:

class Wrapper {
   public MyPojo MyPojo;
}

et envelopper/décompresser dans votre code?

Au-delà de cela, il serait utile de savoir POURQUOI vous souhaitez une entrée d’objet json supplémentaire comme celle-ci? Je sais que cela est fait par les bibliothèques qui émulent JSON via l'API XML (à cause de l'impédance entre XML et JSON, en raison de la conversion de XML à JSON), mais pour les solutions JSON pures, ce n'est généralement pas nécessaire.

Est-ce pour vous permettre de déterminer le type réel? Si c'est le cas, vous pourriez peut-être envisager d'activer les informations de type polymorphes activées pour laisser Jackson le gérer automatiquement? (voir Notes de version 1.5 , entrée pour PTH, pour plus de détails).

1
StaxMan

Je serais intéressé d'entendre la solution du PO pour cela. J'ai des problèmes similaires lorsque mon service Web RESTful sérialise des objets au format XML ou JSON pour les clients. Les clients Javascript doivent connaître le type d’emballage pour pouvoir l’analyser. Coupler le type à un modèle d'URI n'est pas une option.

Merci.

Edit: j'ai remarqué que Spring MappingJacksonJsonMarshaller ajoute la classe d'habillage lors du marshalling. J'ai donc exploré le code dans le débogage et remarqué que Spring passe dans un HashMap avec une paire paire clé-valeur telle que la clé est le nom d'habillage et la valeur objet. J'ai donc étendu JacksonJaxbJsonProvider, substitué la méthode writeTo () et ajouté ce qui suit:

HashMap<String, Object> map = new HashMap<String, Object>();
map.put(value.getClass().getSimpleName(), value);
super.writeTo(map, type, genericType, annotations, mediaType, httpHeaders,entityStream);

C'est un peu un bidouillage, mais cela fonctionne bien.

0
Tony

J'ai constaté par expérience que c'est une bonne idée pour tous les JSON d'inclure à la fois le type backend (sous forme de chaîne) et le type de composant utilisé pour le rendre dans l'interface frontale (si vous utilisez quelque chose comme angular ou Vue).

Cela est justifié par le fait que vous pouvez traiter différents types avec un seul jeu de code. 

En vue, par exemple, avoir le nom du composant d'interface utilisateur dans les données vous permet, entre autres, de disposer d'un écran affichant une liste d'enfants de types différents en utilisant une seule balise dans le modèle parent.

  <component :is="child.componentType"/>.

Pour les systèmes dorsaux et les services Web, je préfère utiliser une seule classe de processeur de service Web fournissant la journalisation, l'audit et la gestion des exceptions pour tous les services Web en recherchant la classe de processeur appropriée en fonction de la charge entrante. La mise en œuvre de tous mes services Web a donc exactement le même aspect (environ 3 lignes de code), et je reçois une journalisation détaillée des événements tout au long du cycle de vie de l'appel sans écrire de code par service pour le faire.

Avoir le type qui enveloppe le JSON le rend auto-documentant. Si tout ce que vous voyez sont les propriétés, vous ne pouvez pas savoir ce que vous regardez jusqu'à ce que vous trouviez le point final correspondant.

Si vous souhaitez écrire un logiciel piloté par les données, il est essentiel de pouvoir identifier ce que vous traitez.

0
Rodney P. Barbati
@JsonTypeInfo(include=As.WRAPPER_OBJECT, use=Id.NAME) 

Cette annotation fonctionne parfaitement, comme suggéré par Arun Prakash. J'essayais d'obtenir JSON sous cette forme. 

{"Rowset":{"ROW":{"receiptno":"881604199388936","status":"SUCCESS"}}}

mais comme ça:

{"ROW":{"receiptno":"881604199388936","status":"SUCCESS"}}}

Maintenant, cette annotation a résolu mon problème.

0
Naushad Idrisi

utiliser withRootName.

objectMapper.writer().withRootName(MyPojo.class.getName());
0
winry