web-dev-qa-db-fra.com

Désérialiseur JSON personnalisé Spring @RestController

Je souhaite utiliser le désérialiseur JSON personnalisé pour certaines classes ( Role here) mais je ne parviens pas à le faire fonctionner Le désérialiseur personnalisé n'est tout simplement pas appelé.

J'utilise Spring Boot 1.2.

Désérialiseur:

public class ModelDeserializer extends JsonDeserializer<Role> {

    @Override
    public Role deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        return null; // this is what should be called but it isn't
    }
}

Manette: 

@RestController
public class RoleController {

    @RequestMapping(value = "/role", method = RequestMethod.POST)
    public Object createRole(Role role) {
        // ... this is called
    }
}
  1. @JsonDeserialize sur le rôle

    @JsonDeserialize(using = ModelDeserializer.class)
    public class Role extends Model {
    
    }
    
  2. Jackson2ObjectMapperBuilder bean dans Java Config

    @Bean
    public Jackson2ObjectMapperBuilder jacksonBuilder() {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.deserializerByType(Role.class, new ModelDeserializer());
        return builder;
    }
    

Qu'est-ce que je fais mal?

EDITCela est probablement dû à @RestController car cela fonctionne avec @Controller...

14
jakub.petr

Tout d'abord, vous n'avez pas besoin de remplacer Jackson2ObjectMapperBuilder pour ajouter un désérialiseur personnalisé. Cette approche doit être utilisée lorsque vous ne pouvez pas ajouter d'annotation @JsonDeserialize. Vous devez utiliser @JsonDeserialize ou remplacer Jackson2ObjectMapperBuilder.

Ce qui manque, c'est l'annotation @RequestBody:

@RestController
public class JacksonCustomDesRestEndpoint {

    @RequestMapping(value = "/role", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public Object createRole(@RequestBody Role role) {
        return role;
    }
}

@JsonDeserialize(using = RoleDeserializer.class)
public class Role {
    // ......
}

public class RoleDeserializer extends JsonDeserializer<Role> {
    @Override
    public Role deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        // .................
        return something;
    }
}
22
Ilya Ovesnov

Il existe également une autre solution intéressante qui peut s'avérer utile si vous souhaitez modifier votre corps JSON avant d'appeler le désérialiseur par défaut. Et imaginons que vous deviez utiliser un bean supplémentaire pour cela (utilisez le mécanisme @Autowire)

Imaginons que vous ayez le contrôleur suivant:

@RequestMapping(value = "/order/product", method = POST)
public <T extends OrderProductInterface> RestGenericResponse orderProduct(@RequestBody @Valid T data) {
    orderService.orderProduct(data);
    return generateResponse();
}

OrderProductInterface est:

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonSerialize(include = NON_EMPTY)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, visible = true, property = "providerType")
@JsonSubTypes({
              @JsonSubTypes.Type(value = OrderProductForARequestData.class, name = "A")
          })
public interface OrderProductInterface{}

Le code ci-dessus fournira une base de désérialisation dynamique sur la variable providerType archivée et une validation en fonction de la mise en œuvre concrète. Pour une meilleure compréhension, considérez que OrderProductForARequestData peut être quelque chose comme ça:

public class OrderProductForARequestData implements OrderProductInterface {

    @NotBlank(message = "is mandatory field.")
    @Getter @Setter
    private String providerId;

    @NotBlank(message = "is mandatory field.")
    @Getter @Setter
    private String providerType;

    @NotBlank(message = "is mandatory field.")
    @Getter @Setter
    private String productToOrder;

}

Et imaginons maintenant que nous voulons lancer providerType (enrichir l'entrée) avant que la désérialisation par défaut ne soit exécutée. donc l'objet sera désérialisé correctement conformément à la règle dans OrderProductInterface. Pour cela, vous pouvez simplement modifier votre classe @Configuration de la manière suivante: 

//here can be any annotation which will enable MVC/Boot 
@Configuration
public class YourConfiguration{

    @Autowired
    private ObjectMapper mapper;

    @Autowired
    private ProviderService providerService;

    @Override
    public void setup() {
        super.setup();
        SimpleModule module = new SimpleModule();
        module.setDeserializerModifier(new BeanDeserializerModifier() {
            @Override
            public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {

                if (beanDesc.getBeanClass() == OrderProductInterface.class) {
                    return new OrderProductInterfaceDeserializer(providerService, beanDesc);
                }
                return deserializer;
            }
        });

        mapper.registerModule(module);
    }

    public static class OrderProductInterfaceDeserializer extends AbstractDeserializer {

            private static final long serialVersionUID = 7923585097068641765L;

            private final ProviderService providerService;

            OrderProductInterfaceDeserializer(roviderService providerService, BeanDescription beanDescription) {
                super(beanDescription);
                this.providerService = providerService;
            }

            @Override
            public Object deserializeWithType(JsonParser p, DeserializationContext context, TypeDeserializer typeDeserializer) throws IOException {
                ObjectCodec oc = p.getCodec();
                JsonNode node = oc.readTree(p);

                //Let's image that we have some identifier for provider type and we want to detect it
                JsonNode tmp = node.get("providerId");
                Assert.notNull(tmp, "'providerId' is mandatory field");
                String providerId = tmp.textValue();
                Assert.hasText(providerId, "'providerId' can't be empty");

                // Modify node
                ((ObjectNode) node).put("providerType",providerService.getProvider(providerId));

                JsonFactory jsonFactory = new JsonFactory();
                JsonParser newParser = jsonFactory.createParser(node.toString());
                newParser.nextToken();

                return super.deserializeWithType(newParser, context, typeDeserializer);

           }

      }
}
0
Sergey Povisenko