POST MongoDB Extended JSON to a RepositoryRestCont

2019-07-29 07:35发布

问题:

I'm looking for a smart or elegant way to POST MongoDB Extended JSON to a REST controller of a web service developped with Spring Boot (1.5.1).

My POJO contains well defined fields (mostly Strings), but also a Map<String, Object> data with is used to store generic data, meaning its specific structure is unknown at build time, and it will be fed by clients of the webservice. Please don't scold me, I know this is a poor design choice, but I was still asked to "make it happen" :)

At some point, a client POST data that includes an ObjectId (in its usual 24 hexadecimal characters form) similar to this:

{
   ... // other fields that pose no problem
   "data" : {
      "foo" : { "$oid" : "56bb5d05f5dbad5dc6f31af7" }
    }
}

With the controller generated by Spring, I get an "Invalid BSON field name $oid" java.lang.IllegalArgumentException.

Using a custom RestController, I can simply bind the request body to String instead of a FooBar object:

@RestController
@RequestMapping("/foobars")
public class FooBarController {

    // custom repository and resource assembler
    private final FooBarRepository repository;
    private final FooBarResourceAssembler resourceAssembler;

    // autowired constructor
    public ReferenceController(FooBarRepository referenceRepository,
                               FooBarResourceAssembler resourceAssembler) {
        this.repository = referenceRepository;
        this.resourceAssembler = resourceAssembler;
    }

    @PostMapping
    public ResponseEntity<Resource<FooBar>> create(@RequestBody String jsonString) {
        // parse the string using com.mongodb.util.JSON
        DBObject object = (DBObject)JSON.parse(jsonString);

        // convert to a domain object
        FooBar foobar = mongoTemplate.getConverter().read(FooBar.class, object);

        // persist to the database
        foobar = foobar.insert(foobar)

        return ResponseEntity.ok(resourceAssembler.toResource(foobar));
    }
}

The problem is I lose the benefit of having the other endpoints handle by Spring (GET /foobar for instance). So I need to use a RepositoryRestController instead of a RestController.

However, if I do that, then POSTing the data mentionned above gives me a ,"message":"Could not read document: Can not deserialize instance of java.lang.String out of START_OBJECT token" org.springframework.http.converter.HttpMessageNotReadableException.

So I need to take this a step further and work with directly with the http servlet request:

@RepositoryRestController
@RequestMapping("/foobars")
public class FooBarController {

    ...

    @PostMapping
    public ResponseEntity<Resource<FooBar>> create(HttpServletRequest request) throws IOException {

        // get the raw JSON string from the request
        String jsonString = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));

        // parse the string using com.mongodb.util.JSON
        DBObject object = (DBObject)JSON.parse(jsonString);

        // convert to a domain object
        FooBar foobar = mongoTemplate.getConverter().read(FooBar.class, object);

        // persist to the database
        foobar = foobar.insert(foobar)

        return ResponseEntity.ok(resourceAssembler.toResource(foobar));
    }
}

This works like a charm, my custom endpoint accepts the MongoDB extended JSON and Spring handles the other common endpoints. I'm just interested in alternative ways to achieve the same, that could prevent me from adding this custom controller... some configuration option for the HTTP message converter or for the Jackson mapper... or anything else I could have missed.

Thanks in advance for your thoughts about this.