I have a simple requirement where, if application encounters an exception, 
my JAX-RS Rest endpoint should return a custom JSON response with 500 HTTP 
header status.

Data needed to construct the response comes from an object with several 
properties (see below). The problem is, I am only interested in one or two 
values from each property (out of several dozens). And I cannot modify any 
of these models/classes (some have a Jackson annotation for JSON 
processing, e.g. null properties should be discarded during serialization). 

    public class MainObject {
      private FirstProperty firstProperty;
      private SecondProperty secondProperty;
      private ThirdProperty thirdProperty;
      // other codes
      public String toString() {
        ObjectMapper mapper = new ObjectMapper();
        try { return mapper.writeValueAsString(this); }
        catch (Exception e) {  return null; }
      }
    }

    public class FirstProperty {
      private boolean bol = true;
      private double dob = 5.0;
      private List<String> subProperty;
      // other properties
      public String toString() {
        ObjectMapper mapper = new ObjectMapper();
        try { return mapper.writeValueAsString(this); }
        catch (Exception e) {  return null; }
      }
    }

    @JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
    public class SecondProperty {
      private String str;
      private List<String> subProperty;
      // other properties
      public String toString() {
        ObjectMapper mapper = new ObjectMapper();
        try { return mapper.writeValueAsString(this); }
        catch (Exception e) {  return null; }
      }
    }

    @JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
    public class ThirdProperty {
      private int intProp = true;
      private List<String> subProperty;
      // other properties
      public String toString() {
        ObjectMapper mapper = new ObjectMapper();
        try { return mapper.writeValueAsString(this); }
        catch (Exception e) {  return null; }
      }
    }

The expected JSON that I should be seeing coming back is on the client side 
(say, a browser -- testing in Edge):

    {
      "firstProperty" : { "subProperty" : [ "val1" ] },
      "secondProperty" : { "str" : "val2", "subproperty" : [ "val3", "val6" 
] },
      "thirdProperty" : { "subProperty" : [ "val4" ] }
    }

Instead, all my field names and their values have their quotations escaped, 
and extra double quotes around the entire value, e.g.:

    {
      "firstProperty" : "{ \"subProperty\" : [ \"val1\" ] }",
      "secondProperty" : "{ \"str\" : \"val2\", \"subproperty\" : [ 
\"val3\", \"val6\" ] }",
      "thirdProperty" : "{ \"subProperty\" : [ \"val4\" ] }"
    }

Please note the extra `"` before and after the curly brackets. My 
environment is:

    Java 1.8.45
    FasterXML Jackson 2.9.8
    Spring Boot 2.0.1
    RestEasy (JBoss) JAX-RS
    JBoss 6.4

I eliminated the majority of "noise" in the code to see at what point this 
happens. This is the controller:


    @Path("/"
    public class MainController {
      
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      @Path("/rest/path")
      public MainObject getMainObject throws MyCustomException {
        // A service call that throws MyCustomException
      }
    }

And JAX-RS `ExceptionMapper` where I send the response back:

    @Provider
    public class MyCustomExceptionMapper extends 
ExceptionMapper<MyCustomException> {
    
      @Override
      public Response toResponse(MyCustomException ex) {
        HashMap<String, Object> responseBody = new HashMap<>();
    
        String strEx = ex.getStrEx(); // Comes from SecondProperty.str 
stored in MyCustomException, not that it matters
    
        // Instantiate an empty object that contains 
        MainObject obj = new MainObject();
        obj.getFirstProperty().setSubProperty(ex.getStrs());
        obj.getSecondProperty().setStr(strEx);
        obj.getSecondProperty().setSubProperty(ex.getStrs());
        obj.getThirdProperty().setSubProperty(ex.getStrs());
    
        responseBody.put("firstProperty", 
serializeFirstProperty(obj.getFirstProperty()));
        responseBody.put("secondProperty", 
serializeSecondProperty(obj.getSecondProperty()));
        responseBody.put("thirdProperty", 
serializeThirdProperty(obj.getThirdProperty()));
        
        Response response = Response.status(/* 500 status 
*/).entity(responseBody).build();
    
        return response;
      }
    
    }

Since I only need to send back a very small subset of overall properties 
from each of my types, I created a custom `StdSerializer` that would only 
populate a needed property. For brevity, I only do 
`serializeFirstProperty()` but they are all more or less identical:

    public StdSerializer<FirstProperty> getFPSerializer(FirstProperty 
firstProperty) {
      return new StdSerializer<FirstProperty>(FirstProperty.class) {
        @Override
        public void serialize(FirstProperty value, JsonGenerator gen, 
SerializerProvider provider) throws IOException {
    
          gen.writeStartObject();
          if (/* there are items in FirstProperty.subProperty */) {
            gen.writeArrayFieldStart("subProperty");
            for (String str : value.getSubProperty())
              gen.writeString(str);
            gen.writeEndArray();
          }
          gen.writeEndObject();
        }
    }

    public <T> ObjectMapper getCustomOM(StdSerializer<?> serializer) {
      ObjectMapper mapper = new ObjectMapper();
    
      SimpleModule sm = new SimpleModule();
      sm.addSerializer(serializer);
      mapper.registerModule(module);
    
      return mapper;
    }

Then use these helper methods like:

    public String serializeFirstProperty(FirstProperty firstProperty) {
      ObjectMapper mapper = getCustomOM(getFPSerializer(firstProperty));
    
      String ser = null;
      try { ser = mapper.writeValueAsString(firstProperty); }
      catch (JsonProcessingException e) { return null; }
      return ser;
    }

I have tried countless of configurations with `ObjectMapper`, e.g. 
`disable(JsonParser.Feature.ALLOW_BACKLASH_ESCAPING_ANY_CHARACTER)` 
(couldn't find any relevant flag for `JsonGenerator` which I really want to 
disable in a similar fashion).

Or explicitly returning `Object` from `serializeFirstProperty()`.

Or set custom `StdSerializer`'s `JsonGenerator.setCharacterEscapes(new 
CharacterEscapes() { //... }` or play around with JAX-RS `Response` at no 
avail. I always seem to get a "string" value with quotations, e.g.:

    "firstProperty" : "{ \"subProperty\" : [ \"val1\" ] }"

If I simply just do 

    responseBody.put("firstProperty", 
mapper.writeValueAsString(obj.getFirstProperty()));

somehow this produces the right JSON output, however, it includes a lot of 
unnecessary properties which I don't want in this exception handling case.

Funny thing is, when I peer into `response` (or `responseBody` map), 
everything looks right (I don't see values having double quotations).

Please also note that not only I can't modify the models, but some of their 
properties are instantiated during creation with default values, so 
not-null inclusion doesn't work, and they will appear in the final JSON if 
I don't use a custom serialization.

Does anyone know what's causing this escaped and extra quotations?

-- 
You received this message because you are subscribed to the Google Groups 
"jackson-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/jackson-user/ef031f10-e369-466d-9418-1ade1e37bf2f%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to