如何反序列化json并区分`null`值和缺少值的存在
如何反序列化json并区分`null`值和缺少值的存在
我有一个REST API,在客户端调用带有请求体的POST请求时,后端在反序列化后应该区分null和值的缺失。
因为如果JSON中的值为null,那么数据库中的值应该变为null。
如果JSON中的值缺失,那么数据库中的值应该保持不变。
JSON示例:
{
"id" : 1,
"name" : "样例名称",
"value" : null
}
或者
{
"id" : 1,
"name" : "样例名称"
}
在Java中反序列化后,看起来像这样:value = null;
Java示例:
@Entity
@Table("sample")
public class Sample {
@Id
@Column
private Long id;
@Column
private String name;
@Column
private Integer value;
// 获取器/设置器
}
示例REST请求:
@PutMapping
public ResponseEntity
return ResponseEntity.ok(service.updateSample(dto));
}
示例服务实现:
public SampleDto updateSample(SampleDto dto) {
Sample sample = sampleRepository.findById(dto.getId);
sample.setName(dto.getName());
sample.setValue(dto.getValue());
// 在此操作中,后端需要理解:值是null还是缺失
// 因为如果JSON中的值为null,那么数据库中的值应该变为null
// 如果JSON中的值缺失,那么数据库中的值应该保持不变
Sample newSample = sampleRepository.save(sample);
return modelMapper.map(newSample, SampleDto.class);
}
该项目使用Spring Data。
也许我应该使用@JsonDeserialize注解或其他Hibernate注解。
我尝试使用@JsonDeserialize,但它不是解决方案。
如何反序列化JSON并区分`null`和缺少值的出现的原因以及解决方法
在进行部分更新时,与完整资源更新不同,我们应该采用不同的方法来实现。让我们创建两个请求`POJO`类。一个类将用于创建和更新资源,另一个类将用于部分更新给定资源。为了强调这一点,我们将使用不同的`HTTP`方法。为了区分`null`和`缺少值`,我们可以使用`java.util.Optional`类。
`SampleCompleteRequest`类将与`POST`(创建)和`PUT`(更新)方法一起使用。
`SamplePartialRequest`类将与`PATCH`(部分更新)方法一起使用。
为了避免在此示例中出现样板代码,我使用了`Lombok`和`MapStruct`,但这不是必需的。
模型
import jakarta.validation.constraints.NotBlank; import lombok.Data; public class SampleCompleteRequest { private String name; private String value; } import jakarta.validation.constraints.NotBlank; import lombok.Data; import java.util.Optional; public class SamplePartialRequest { private Optionalname; private Optional value; } import lombok.Data; public class SampleResponse { private Long id; private String name; private String value; } import lombok.Data; public class Sample { // - Hibernate annotations are removed private Long id; private String name; private String value; }
MapStruct
在`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; (unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = SPRING) public interface SamplesMapper { (nullValueCheckStrategy = ALWAYS, nullValuePropertyMappingStrategy = IGNORE) Sample patch(SamplePartialRequest input, Sample target); Sample update(SampleCompleteRequest input, Sample target); SampleResponse mapToResponse(Sample input); default String optionalToString(Optionalnullable) { return nullable == null ? null : nullable.orElse(null); } }
插件将为我们生成样板代码。下面的类是自动生成的,我们不需要手动实现它。
public class SamplesMapperImpl implements SamplesMapper { 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; } public Sample update(SampleCompleteRequest input, Sample target) { if ( input == null ) { return target; } target.setName( input.getName() ); target.setValue( input.getValue() ); return target; } 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; (value = "/api/v1/samples") public class SamplesResource { private final SamplesMapper mapper; private final SamplesService samplesService; public CollectionModellistAll() { List entities = samplesService.list().stream().map(mapper::mapToResponse).toList(); return CollectionModel.of(entities); } public EntityModel addSample( SampleCompleteRequest request) { var entity = samplesService.create(request); var response = mapper.mapToResponse(entity); return EntityModel.of(response); } (path = "{id}") public EntityModel updateSample( Long id, SampleCompleteRequest request) { var entity = samplesService.update(id, request); var response = mapper.mapToResponse(entity); return EntityModel.of(response); } (path = "{id}") public EntityModel partiallyUpdateSample( Long id, 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; public class SamplesService { private final SamplesMapper mapper; private final SamplesRepository repository; public Listlist() { 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); } }
另请参阅: