简体   繁体   中英

How to Deserialize missing properties to default values with jackson in java

I am trying to make jackson deserialize missing properties that represent collections into empty collections instead of NULL. I've tried a few different things and this is the latest that isn't working.

If there is a way to make any potential solution globally configured for all POJO deserialization that would be great.

Given the following json, which is missing the property 'assets':

{
    "name":"my-layer",
    "code": "ly1",
    "types":["type1", "type2"],
    "private": false
}

Given the following POJO:

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Builder.Default;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class CreateLayerRequest {

  @NotBlank
  @JsonProperty(required = true)
  private String name;

  @NotBlank
  @JsonProperty(required = true)
  private String code;

  @Default
  @NotNull
  @JsonProperty(value = "private", required = true)
  private Boolean privateLayer = Boolean.FALSE;

  @Default
  @JsonSetter(nulls = Nulls.AS_EMPTY)
  @JsonProperty("types")
  private Set<UUID> types = new HashSet<>();

  @Default
  @JsonSetter(nulls = Nulls.AS_EMPTY)
  @JsonProperty("assets")
  private Set<UUID> assets = new HashSet<>();
}

configured my fix to be global

@Configuration
public class MyAppConfiguration implements WebMvcConfigurer {

  /**
   * allows POJOs using json filtering to serialize all properties when not being filtered
   */
  @Override
  public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    for (HttpMessageConverter<?> converter : converters) {
      if (converter instanceof MappingJackson2HttpMessageConverter) {
        ((MappingJackson2HttpMessageConverter) converter)
            .getObjectMapper()
            // not related to this thread, this is for property filtering when serializing POJOs
            .setFilterProvider(new SimpleFilterProvider().setFailOnUnknownId(false))
            // these modules are related to to this thread
            .registerModule(
                new SimpleModule().addDeserializer(Set.class, new CollectionDeserializer())
            )
            .registerModule(
                new SimpleModule().addDeserializer(List.class, new CollectionDeserializer())
            )
            .registerModule(
                new SimpleModule().addDeserializer(Map.class, new CustomMapDeserializer())
            );
      }
    }
  }

  @Slf4j
  @NoArgsConstructor
  @AllArgsConstructor
  private static class CollectionDeserializer<T>
      extends JsonDeserializer<Collection<T>>
      implements ContextualDeserializer {

    private JavaType type;
    private final ObjectMapper mapper = new ObjectMapper();

    @Override
    public JsonDeserializer<?> createContextual(
        DeserializationContext deserializationContext,
        BeanProperty beanProperty) {

      final JavaType javaType = deserializationContext.getContextualType() != null
          ? deserializationContext.getContextualType()
          : beanProperty.getMember().getType();

      return new CollectionDeserializer<>(javaType);
    }

    @Override
    public Collection<T> deserialize(
        JsonParser jsonParser,
        DeserializationContext deserializationContext) throws IOException {

      try {
        if (Set.class.isAssignableFrom(type.getRawClass())) {
          return deserializeJsonToCollection(jsonParser, new HashSet<>());
        }

        if (List.class.isAssignableFrom(type.getRawClass())) {
          return deserializeJsonToCollection(jsonParser, new ArrayList<>());
        }
      } catch (IllegalArgumentException e) {
        log.warn("unable to deserialize array property in request");
        throw e;
      }

      final String message = "unable to deserialize collection";
      log.warn(message);
      throw new CollectionDeserializationException(message);
    }

    private Collection<T> deserializeJsonToCollection(
        JsonParser jsonParser,
        Collection<T> collection) throws IOException {

      ((ArrayNode) jsonParser
          .getCodec()
          .readTree(jsonParser))
          .forEach(item -> {
            try {
              final T value = mapper.readValue(
                  String.format("\"%s\"", item.textValue()),
                  type.getContentType()
              );
              collection.add(value);
            } catch (IOException e) {
              final String message = String.format(
                  "unable to deserialize value [%s] to type [%]",
                  item.textValue(),
                  type.getContentType()
              );
              log.warn(message);
              throw new CollectionDeserializationException(message);
            }
          });

      return collection;
    }

    @Override
    public Collection<T> getNullValue(DeserializationContext ctxt) {
      if (Set.class.isAssignableFrom(type.getRawClass())) {
        return new HashSet<>();
      }

      if (List.class.isAssignableFrom(type.getRawClass())) {
        return new ArrayList<>();
      }

      return null;
    }
  }

  @Slf4j
  private static class CustomMapDeserializer<T, K> extends JsonDeserializer<Map<T, K>> {

    private final ObjectMapper mapper = new ObjectMapper();
    private final TypeReference<Map<T, K>> mapTypeReference = new TypeReference<>() {
    };

    @Override
    public Map<T, K> deserialize(
        JsonParser jsonParser,
        DeserializationContext deserializationContext) {
      try {
        return mapper.readValue(jsonParser, mapTypeReference);
      } catch (Exception e) {
        final String message = "unable to deserialize map";
        log.warn(message);
        throw new MapDeserializationException(message, e);
      }
    }

    @Override
    public Map<T, K> getNullValue(DeserializationContext ctxt) {
      return new HashMap<>();
    }
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM