I have a field in a POJO which needs to be encrypted before being serialized and, similarly, decrypted upon deserialization.
The issue is that a cryptor is a Spring bean, so I need to access Spring context in my custom serializers/deserializers.
That's how I do that now:
private static final Cryptor cryptor = ApplicationContextUtils.getApplicationContext().getBean(Cryptor.class);
I wonder though if it's possible to autowire cryptor without accessing the context manually.
Converting the serializer/deserializer to Spring beans doesn't help, as Jackson creates an instance of a serializer/deserializer using a no-args constructor, so an autowired field cryptor remains null.
A bit of code to illustrate what I'm talking about:
public class POJO {
#JsonSerialize(using = EncryptSerializer.class)
#JsonDeserialize(using = DecryptDeserializer.class)
private String code;
}
public class EncryptSerializer extends JsonSerializer<String> {
private static final Cryptor cryptor = ApplicationContextUtils.getApplicationContext().getBean(Cryptor.class);
#Override
public void serialize(String value, JsonGenerator generator, SerializerProvider serializers) throws IOException {
if (value != null) {
generator.writeString(cryptor.encrypt(value));
}
}
}
public class DecryptDeserializer extends JsonDeserializer<String> {
private static final Cryptor cryptor = ApplicationContextUtils.getApplicationContext().getBean(Cryptor.class);
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
String value = jsonParser.getValueAsString();
return (value != null) ? cryptor.decrypt(value) : null;
}
}
Thanks in advance.
Yes, it can. Just use #JsonComponent annotation.
I can recommend u to see baeldung article about this theme: https://www.baeldung.com/spring-boot-jsoncomponent
UPD:
here javadoc for #JsonComponent annotation: https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/jackson/JsonComponent.html
Related
I would like to create a customized Jackson annotation like that:
#JacksonAnnotationsInside
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.TYPE})
//... Other Jackson annotations...
#interface SelfLink {
Class<? extends Entity> type();
String uriTemplate() default "";
}
I would like to use this annotation on classes or fields like this:
#Getter
#SelfLink(type = Alpha.class)
class Alpha extends Entity {
private String name;
public Alpha(Long id, String name) {
super(id);
this.name = name;
}
}
#Getter
class Beta {
private String uuid;
#SelfLink(type = Gamma.class)
private Entity data;
}
#Getter
class Gamma extends Entity {
private String stuff;
public Gamma(Long id, String stuff) {
super(id);
this.stuff = stuff;
}
}
The customized Jackson annotation would be used to append extra fields. I tried to use #JsonSerialize(using = SelfLinkSerializer.class) on the SelfLink annotation to define a Serializer:
#JsonComponent
class SelfLinkSerializer extends StdSerializer<Object> {
private Gson gson = new Gson();
private Mirror mirror = new Mirror();
#Autowired
private LinkResolver linkResolver;
#SuppressWarnings("unused")
private SelfLinkSerializer() {
this(Object.class,
new LinkResolver() {
#Override
public <T extends Entity> String resolve(Class<? extends Entity> type, T instance) {
return "always-null";
}
}
);
}
SelfLinkSerializer(Class<Object> t, LinkResolver linkResolver) {
super(t);
this.linkResolver = linkResolver;
}
#Autowired
public SelfLinkSerializer(LinkResolver linkResolver) {
this(Object.class, linkResolver);
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
this.serializeContentWithoutThisSerializer(value, gen, provider);
gen.writeEndObject();
}
private void serializeContentWithoutThisSerializer(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
String jsonContent = this.serializeContentViaGson(value, gen, provider);
gen.writeRaw(jsonContent);
}
private String serializeContentViaGson(Object value, JsonGenerator gen, SerializerProvider provider) {
JsonElement jsonElement = this.gson.toJsonTree(value);
SelfLink selfLink = this.mirror.on(value.getClass()).reflect().annotation(SelfLink.class).atClass();
if(selfLink != null) {
Class<? extends Entity> type = selfLink.type();
String link = value instanceof Entity ? this.linkResolver.resolve(type, (Entity) value) : null;
jsonElement.getAsJsonObject().addProperty("link", link);
}
String json = this.gson.toJson(jsonElement);
String trimmed = CharMatcher.is('{').trimFrom(json);
trimmed = CharMatcher.is('}').trimTrailingFrom(trimmed);
return trimmed;
}
}
The original idea was to start the JSON ("{"), use Jackson engine to generate the content, my serializer would resolve the link, append it on the JSON ("link":"..."), and close the JSON ("}"). But I hit a wall: I did not find a way reuse Jackson. My workaround so far is to use Gson, but that is a monstrosity. Gson do not honor Jackson annotations. I would need to create an algorithm to convert all Jackson annotations and that is a "NO-NO".
I am kinda locked on some versions of Spring Boot and Jackson (1.5.8.RELEASE and 2.8.10 respectively). I created a Gist with the whole example, and a pom.xml.
I have a secondary issue because even as a #JsonComponent, Spring is not injecting the #Autowired LinkResolver linkResolver. For now I also do not know how to make #SelfLink work on fields like on Beta class. Bur for now my question is:
How is it possible to use the Jackson API to generate the string with the JSON representation for the value param in the public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException method?
I have the following code with Jackson:
public class Header implements Serializable {
#JsonProperty("numeroUnico")
private Integer numeroCliente;
#JsonProperty("oficina")
private Integer oficina;
#JsonProperty("fecha")
#JsonSerialize(using = CustomDateSerializer.class)
private Date fechaInscripcion;
}
this is my class "CustomDateSerializer.class"
public class CustomDateSerializer extends StdSerializer<Date> {
private SimpleDateFormat formatter
= new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
public CustomDateSerializer() {
this(null);
}
public CustomDateSerializer(Class t) {
super(t);
}
#Override
public void serialize (Date value, JsonGenerator gen, SerializerProvider arg2)
throws IOException, JsonProcessingException {
gen.writeString(formatter.format(value));
}
}
They asked me to migrate all the implementations of Jackson to Gson.
Taking into account that the notation in Jackson #JsonProperty has an equivalence in Gson that is #SerializedName.
But for the notation in Jackson of:
#JsonSerialize (using = CustomDateSerializer.class)
What is its equivalent for Gson? if not, as it should be the implementation for attributes of type Date in my DTO.
I think the closest and probably the only match is #TypeAdapter. However you need to code either JsonSerializer<T> or TypeAdapter<T> to be used with that annotation.
For example how to make something like your CustomDateSerializer see accepted answer for this question.
Or maybe you can wrap your existing CustomDateSerializer with Gson TypeAdapter<Date> and use that in the annotation.
How can I access #JacksonInject values from inside a Jackson custom deserializer or converter? Please provide complete source-code for:
An entry point that configures the ObjectMapper.
A class whose constructor requires an injected value.
A custom deserializer for the above class.
I saw DeserializationContext.findInjectableValue() but I wasn't sure how to construct the corresponding BeanProperty.
I also took a look at #JsonDeserialize(converter = ...) but didn't see any way to pass injected values into the converter. Is this supported?
Here is my (untested) answer:
public class JacksonDeserializer extends StdDeserializer<MyPojo>
implements ContextualDeserializer
{
private static final long serialVersionUID = 1L;
/**
* The JSON property being deserialized (null if root object).
*/
private final BeanProperty property;
JacksonDeserializer()
{
super(MyPojo.class);
this.property = null;
}
/**
* #param property the JSON property being deserialized (null if root object)
*/
JacksonDeserializer(BeanProperty property)
{
super(MyPojo.class);
this.property = property;
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException
{
return new JacksonDeserializer(property);
}
#Override
public MyPojo deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException
{
MyInjectable injectable = (MyInjectable) ctxt.findInjectableValue("myInjectable", property, null);
assert (scope != null);
// The rest of the deserializer goes here
}
}
I hope this helps.
I have a custom JsonSerializer for a field (simplified code):
#JsonSerialize(using=Text1Serializer.class)
#JsonProperty("text1") // I need this inside the custom serializer
#Override
public String getTextOne() {
return "foo";
}
// ...
public static class Text1Serializerextends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
// how to get "text1" here?
provider.defaultSerializeValue(value, jgen);
}
}
Since I need to serialize about ten other fields with a similar logic, that just depends on the field name, it would help me very much if I could get the property name inside the custom serializer - instead of writing ten identical serializers.
I've seen that inside the serialize() method I can get the whole object with JsonGenerator.getCurrentValue() (see this answer), but I didnt' find a way to get the field name.
I'm using Jackson 2.6
You can get field name in a custom json serializer like this:
#JsonComponent
public class Text1Serializerextends extends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
// will output "text1"
System.out.println(jgen.getOutputContext().getCurrentName());
provider.defaultSerializeValue(value, jgen);
}
}
If you implement ContextualSerializer, this will be used to produce a "contextual" version of your serializer, i.e. one that is configured using the BeanProperty:
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException;
This should return a new instance that is customised for the given property: it doesn't have to be the same class as the non-customised serializer (although the standard Jackson implementations all seem to work that way).
You can get the current property name by calling jgen.getOutputContext().getCurrentName()
You can achieve this through Customized SerzializerProvider
public class EmptyContentSerializerProvider extends DefaultSerializerProvider {
#Override
public JsonSerializer<Object> findNullValueSerializer(BeanProperty property) throws JsonMappingException {
property.getName(); //this can extract the filed name
}
}
I want to serialize few fields of my class in custom way using jackson. So i wrote a custom serializer for this.But my problem is i am not able to get the name of the field in custom serializer. My POJO class is
public static class Foo {
public String foo = "a";
#JsonSerialize(using = CustomSerializer.class)
public String bar = "b";
#JsonSerialize(using = CustomSerializer.class)
public String foobar = "c";
}
And my custom serializer class is
public class CustomSerializer extends JsonSerializer<String>
{
#Override
public void serialize(String t, JsonGenerator jg, SerializerProvider sp) throws IOException, JsonProcessingException
{
if(field.name.equals("a"))
//do this
else if(filed.name.equals("b"))
//do that
}
}
Here i want get the name of field which is being serialized.
How can i get the name of fields "a" and "b" in custom serializer ?
Thanks
I think, this is not possible now. But you can create two separate serializers for each property. I know, this a little workaround, but it should work.