如何反序列化json并区分`null`值和缺少值的存在

13 浏览
0 Comments

如何反序列化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 updateSample(@RequestBody SampleDto dto) {

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,但它不是解决方案。

0
0 Comments

如何反序列化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 Optional name;
    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(Optional nullable) {
        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 CollectionModel listAll() {
        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 List 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);
    }
}

另请参阅:

  1. REST API中的HTTP PUT vs HTTP PATCH
  2. Jackson objectMapper与其他对象映射器之间的区别
  3. Spring MVC PATCH方法:部分更新
0