9

I have a simple data service :

@GET
public Data getData(@QueryParam("id") Long id) {
  Data data = dataService.getData(id);
  return data;
}

And a matching DataSerializer that implements JsonSerializer<Data> : The DataSerializer is registered to Jackson via :

simpleModule.addSerializer(Data.class , dataSerializer);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(simpleModule);

It works well. But today , I want to add another Locale parameter , and hope the DataSerializer to output correspondent content :

@GET
public Data getData(@QueryParam("id") Long id , @QueryParam("locale") Locale locale)

The 'Data' itself contains various locale variations , and I hope to get the assigned locale output.

But when I get the locale from the parameter , I don't know how to pass the locale value to the DataSerializer

Is there anyway to achieve this ?

Except this solution :

Data data = dataService.getData(id.get() , locale);

which is not what I want.

It seems ThreadLocal is the only way to achieve this , but I feel that is ugly. Any other feasible solutions ?

Thanks.

Environments : dropwizard-0.7.0-rc2 , jackson-core:jar:2.3.1

===================== updated ==========

reply to @andrei-i :

Because my data itself already contains various locale versions. for example :

Data helloData = dataService.get("hello");
helloData.getName(Locale.English) == "Hello";
helloData.getName(Locale.France) == "Bonjour";
helloData.getName(Locale.Germany) == "Hallo";

I want to directly pass the locale from URL to JsonSerializer , to get one version of the data presentation.

And there 'may' be other version (not just locale) , so , inheriting Data mixing Locale is not considered.

smallufo
  • 11,516
  • 20
  • 73
  • 111
  • Why not making your Data class locale-aware: introduce a LocaleData class that has the same fields as Data, but translates the translatable stuff? – V G Mar 25 '14 at 13:48
  • hi @@andrei-i , I replied in the content. Thanks. – smallufo Mar 25 '14 at 13:56

2 Answers2

17

I know that this is not a new question but here is what I came up with facing the similar problem:

  1. created custom annotation:

    @Target({ ElementType.FIELD, ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface JsonLocalizable {
    
      public String localizationKey();
    } 
    
  2. Jackson serializer:

     public class LocalizingSerializer extends StdSerializer<String> implements ContextualSerializer {
    
          private String localizationKey;
    
          public LocalizingSerializer() {
            super(String.class);
          }
    
          public LocalizingSerializer(String key) {
            super(String.class);
    
            this.localizationKey = key;
          }
    
          @Override
          public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
    
            String localizedValue = //.... get the value using localizationKey
    
            jgen.writeString(localizedValue);
          }
    
          @Override
          public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
    
            String key = null;
            JsonLocalizable ann = null;
    
            if (property != null) {
              ann = property.getAnnotation(JsonLocalizable.class);
            }
    
            if (ann != null) {
              key = ann.localizationKey();
            }
    
            //if key== null??
    
            return new LocalizingSerializer(key);
          }
        }
    
  3. Annotate the field you want to localize:

    public class TestClass {
    
        @JsonSerialize(using = LocalizingSerializer.class)
        @JsonLocalizable(localizationKey = "my.key")
        private String field;
    
        public String getField() {
          return this.field;
        }
    
        public void setField(String field) {
          this.field = field;
        }
    }
    
Madara's Ghost
  • 172,118
  • 50
  • 264
  • 308
macko
  • 244
  • 3
  • 8
  • Following needs to be added in the custom annotation @JacksonAnnotation for it to be available in the BeanProperty parameter of the createContextual method. – noo Feb 17 '17 at 17:03
2

Solution 1. In your JAX-RS implementation register your own implementation of MessageBodyWriter for JSON requests. Probably your implementation will extend Jackson. Also it might be possible that you will have to unregister Jackson. In a MessageBodyWriter you can inject a UriInfo instance using the @Context annotation, and with it you can get any request parameter.

Solution 2. Change the architecture of your Data, so that it is locale-aware. For example, create a setter setLocale() which will change the returned data, if the locale was set.

V G
  • 18,822
  • 6
  • 51
  • 89
  • H, `DataMessageBodyWriter extends JsonSerializer implements MessageBodyWriter` seems good . But I have to `@Inject JacksonJsonProvider` and `@Inject Provider` . In the injection of `Provider` , it will cause circular dependency (because `ObjectMapperProvider implements Provider` itself needs `DataMessageBodyWriter` to be injected) . I know it is very complicated , maybe solution 2 is easier. – smallufo Mar 26 '14 at 12:41
  • It seems complicated as you explained it, but I meant something easier: extend directly `DataMessageBodyWriter extends JacksonJsonProvider` and simply add an if condition in the extended `writeTo` method before calling super, for handling your `Data` type. – V G Mar 26 '14 at 13:07
  • I use the Solution 2. … Well , I don't thinks it is the best solution. Because in JsonSerializer , there is no mechanism to get info from the Resource layer. When in the Resource layer , there is no way to assign the output decorator of a POJO. (in my example , the decorating parameter is a locale ) – smallufo Mar 28 '14 at 09:48
  • But did you try my idea to extend directly JacksonJsonProvider where you access to the whole request? – V G Mar 28 '14 at 09:50