提问者:小点点

如何反序列化 json 并将“null”与缺少值区分开来


我有 REST api,当客户端使用正文调用 POST 请求时,反序列化后的后端应区分 null 和缺少值。

因为如果JSON中的值为空,那么DB中的value应该为空。

如果 JSON 中的值不存在,则数据库中的值应保持不变。

JSON:

{
  "id" : 1,
  "name" : "sample name",
  "value" : null
}

{
  "id" : 1,
  "name" : "sample name"
}

对于反序列化后的Java,它看起来像:value=null;

Java:

@Entity
@Table("sample")
public class Sample {
    @Id
    @Column
    private Long id;
    @Column
    private String name;
    @Column
    private Integer value;

    // getters / setters
}

样本剩余请求:

@PutMapping
public ResponseEntity<SampleDto> updateSample(@RequestBody SampleDto dto) {
    return ResponseEntity.ok(service.updateSample(dto));
}

示例服务impl:

public SampleDto updateSample(SampleDto dto) {
  Sample sample = sampleRepository.findById(dto.getId);
  sample.setName(dto.getName());
  sample.setValue(dto.getValue());

//In this operation back need understand: value is null or absence
//Because if value in JSON is null, then value in DB should become null
//If value in JSON absence, then value in DB should remain unchanged

  Sample newSample = sampleRepository.save(sample);
  return modelMapper.map(newSample, SampleDto.class);
}

项目使用Spring Data

也许我应该使用@JsonDeserialize注解或其他Hibernate注解

我尝试使用< code>@JsonDeserialize,但它不是解决方案。


共1个答案

匿名用户

部分更新不同于完全资源更新,我们应该以不同的方式实现它。让我们创建两个request POJO类。一个类将用于创建和更新资源,另一个将用于部分更新给定资源。为了强调这一点,我们将使用不同的< code>HTTP方法。为了区分< code>null和< code >缺勤,我们可以使用< code>java.util.Optional类。

    与< code>POST(创建)和< code>PUT(更新)方法一起使用的< Li > < code > SampleCompleteRequest 类。 与< code>PATCH(部分更新)方法一起使用的< Li > < code > SamplePartialRequest 类。

为了避免本例中的样板代码,我使用了Lombok和MapStruct,但这不是必需的。

import jakarta.validation.constraints.NotBlank;
import lombok.Data;

@Data
public class SampleCompleteRequest {

    @NotBlank
    private String name;
    private String value;
}
import jakarta.validation.constraints.NotBlank;
import lombok.Data;

import java.util.Optional;

@Data
public class SamplePartialRequest {
    private Optional<@NotBlank String> name;
    private Optional<String> value;
}
import lombok.Data;

@Data
public class SampleResponse {
    private Long id;
    private String name;
    private String value;
}
import lombok.Data;

@Data
public class Sample {
    //@Id - Hibernate annotations are removed
    private Long id;
    private String name;
    private String value;
}

MapStruct 中,我们需要定义一个包含我们需要的所有方法的接口。

import com.example.demo.model.SampleCompleteRequest;
import com.example.demo.model.SamplePartialRequest;
import com.example.demo.model.SampleResponse;
import jakarta.annotation.Nullable;
import org.mapstruct.BeanMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.ReportingPolicy;

import java.util.Optional;

import static org.mapstruct.MappingConstants.ComponentModel.SPRING;
import static org.mapstruct.NullValueCheckStrategy.ALWAYS;
import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE;

@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = SPRING)
public interface SamplesMapper {
    @BeanMapping(nullValueCheckStrategy = ALWAYS, nullValuePropertyMappingStrategy = IGNORE)
    Sample patch(SamplePartialRequest input, @MappingTarget Sample target);

    Sample update(SampleCompleteRequest input, @MappingTarget Sample target);

    SampleResponse mapToResponse(Sample input);

    default String optionalToString(@Nullable Optional<String> nullable) {
        return nullable == null ? null : nullable.orElse(null);
    }
}

插件将为我们生成样板代码。下面的类是自动生成的,我们不需要手动实现。

@Component
public class SamplesMapperImpl implements SamplesMapper {

    @Override
    public Sample patch(SamplePartialRequest input, Sample target) {
        if ( input == null ) {
            return target;
        }

        if ( input.getName() != null ) {
            target.setName( optionalToString( input.getName() ) );
        }
        if ( input.getValue() != null ) {
            target.setValue( optionalToString( input.getValue() ) );
        }

        return target;
    }

    @Override
    public Sample update(SampleCompleteRequest input, Sample target) {
        if ( input == null ) {
            return target;
        }

        target.setName( input.getName() );
        target.setValue( input.getValue() );

        return target;
    }

    @Override
    public SampleResponse mapToResponse(Sample input) {
        if ( input == null ) {
            return null;
        }

        SampleResponse sampleResponse = new SampleResponse();

        sampleResponse.setId( input.getId() );
        sampleResponse.setName( input.getName() );
        sampleResponse.setValue( input.getValue() );

        return sampleResponse;
    }
}

控制器类很容易实现:

import com.example.demo.model.SampleCompleteRequest;
import com.example.demo.model.SamplePartialRequest;
import com.example.demo.model.SampleResponse;
import com.example.service.SamplesMapper;
import com.example.service.SamplesService;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@AllArgsConstructor
@RestController
@RequestMapping(value = "/api/v1/samples")
public class SamplesResource {

    private final SamplesMapper mapper;
    private final SamplesService samplesService;

    @GetMapping
    public CollectionModel<SampleResponse> listAll() {
        List<SampleResponse> entities = samplesService.list().stream().map(mapper::mapToResponse).toList();

        return CollectionModel.of(entities);
    }

    @PostMapping
    public EntityModel<SampleResponse> addSample(@Valid @RequestBody SampleCompleteRequest request) {
        var entity = samplesService.create(request);
        var response = mapper.mapToResponse(entity);

        return EntityModel.of(response);
    }

    @PutMapping(path = "{id}")
    public EntityModel<SampleResponse> updateSample(@PathVariable Long id, @Valid @RequestBody SampleCompleteRequest request) {
        var entity = samplesService.update(id, request);
        var response = mapper.mapToResponse(entity);

        return EntityModel.of(response);
    }

    @PatchMapping(path = "{id}")
    public EntityModel<SampleResponse> partiallyUpdateSample(@PathVariable Long id, @Valid @RequestBody SamplePartialRequest request) {
        var entity = samplesService.patch(id, request);
        var response = mapper.mapToResponse(entity);

        return EntityModel.of(response);
    }
}

服务类也很简单:

import com.example.demo.model.SampleCompleteRequest;
import com.example.demo.model.SamplePartialRequest;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@AllArgsConstructor
public class SamplesService {

    private final SamplesMapper mapper;
    private final SamplesRepository repository;

    public List<Sample> list() {
        return repository.listAll();
    }

    public Sample create(SampleCompleteRequest request) {
        var sample = mapper.update(request, new Sample());
        return repository.save(sample);
    }

    public Sample update(Long id, SampleCompleteRequest request) {
        var sample = repository.find(id).orElseThrow();
        mapper.update(request, sample);

        return repository.save(sample);
    }

    public Sample patch(Long id, SamplePartialRequest request) {
        var sample = repository.find(id).orElseThrow();
        mapper.patch(request, sample);

        return repository.save(sample);
    }
}

另请参阅:

    < li>HTTP PUT与REST API中的HTTP补丁 < Jackson objectMapper与其他人的区别 < li>Spring MVC修补方法:部分更新

相关问题