Deserialize to String or Object using Jackson - java

I have an object that sometimes looks like this:
{
"foo" : "bar",
"fuzz" : "bla"
}
and sometimes looks like this:
{
"foo" : { "value" : "bar", "baz": "asdf" },
"fuzz" : { "thing" : "bla", "blip" : "asdf" }
}
these classes would look like:
public class Foo {
String value;
String baz;
}
public class Fuzz {
String thing;
String blip;
}
where the first cases are shorthand for the second ones. I would like to always deserialize into the second case.
Further - this is a pretty common pattern in our code, so I would like to be able to do the serialization in a generic manner, as there are other classes similar to Foo above that have the same pattern of using String as a syntactic sugar for a more complex object.
I'd imagine the code to use it would look something like this
public class Thing {
#JsonProperty("fuzz")
Fuzz fuzz;
#JsonProperty("foo")
Foo foo;
}
How do I write a custom deserializer (or some other module) that generically handles both cases?

To make it generic we need to be able to specify name which we would like to set in object for JSON primitive. Some flexibility gives annotation approach. Let's define simple annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#interface JsonPrimitiveName {
String value();
}
Name means: in case primitive will appear in JSON use value() to get property name for given primitive. It binds JSON primitive with POJO field. Simple deserialiser which handles JSON object and JSON primitive:
class PrimitiveOrPojoJsonDeserializer extends JsonDeserializer implements ContextualDeserializer {
private String primitiveName;
private JavaType type;
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonDeserializer<Object> deserializer = ctxt.findRootValueDeserializer(type);
if (p.currentToken() == JsonToken.START_OBJECT) {
return deserializer.deserialize(p, ctxt);
} else if (p.currentToken() == JsonToken.VALUE_STRING) {
BeanDeserializer beanDeserializer = (BeanDeserializer) deserializer;
try {
Object instance = beanDeserializer.getValueInstantiator().getDefaultCreator().call();
SettableBeanProperty property = beanDeserializer.findProperty(primitiveName);
property.deserializeAndSet(p, ctxt, instance);
return instance;
} catch (Exception e) {
throw JsonMappingException.from(p, e.getMessage());
}
}
return null;
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
JsonPrimitiveName annotation = property.getAnnotation(JsonPrimitiveName.class);
PrimitiveOrPojoJsonDeserializer deserializer = new PrimitiveOrPojoJsonDeserializer();
deserializer.primitiveName = annotation.value();
deserializer.type = property.getType();
return deserializer;
}
}
Now we need to annotate POJO fields as below:
class Root {
#JsonPrimitiveName("value")
#JsonDeserialize(using = PrimitiveOrPojoJsonDeserializer.class)
private Foo foo;
#JsonPrimitiveName("thing")
#JsonDeserialize(using = PrimitiveOrPojoJsonDeserializer.class)
private Fuzz fuzz;
// getters, setters
}
I assume that all classes are POJO-s and follow all rules - have getters, setters and default constructor. In case constructor does not exist you need to change this beanDeserializer.getValueInstantiator().getDefaultCreator().call() line somehow which fits your requirements.
Example app:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(jsonFile, Root.class));
}
}
Prints for shortened JSON:
Root{foo=Foo{value='bar', baz='null'}, fuzz=Fuzz{thing='bla', blip='null'}}
And for full JSON payload:
Root{foo=Foo{value='bar', baz='asdf'}, fuzz=Fuzz{thing='bla', blip='asdf'}}

Related

Jackson: add suffix according to a field type

Here my POJO:
public class AutorDenormalized {
private String id;
private Long unitatId;
private String grupId;
private String descripcio;
public AutorDenormalized() {
}
// getters $ setters
}
I'd like to serialise this kind of objects adding a suffix according to field type. I mean,
If field type is a String -> then add a *_s suffix
If field type is a Long -> then add a *_l suffix
Otherwise keep going
Do you have any ideas how to solve it?
You need to implement custom BeanPropertyWriter which can generate property name with a suffix. To register custom BeanPropertyWriter you need to create custom BeanSerializerModifier.
Below example shows simplified implementation which shows a way how to achieve above result:
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.util.NameTransformer;
import java.io.IOException;
import java.util.List;
public class JsonTypeInfoApp {
public static void main(String[] args) throws IOException {
SimpleModule typeSuffixModule = new SimpleModule();
typeSuffixModule.setSerializerModifier(new TypeSuffixBeanSerializerModifier());
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.registerModule(typeSuffixModule);
System.out.println(mapper.writeValueAsString(new AutorDenormalized()));
}
}
class TypeSuffixBeanSerializerModifier extends BeanSerializerModifier {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
for (int i = 0; i < beanProperties.size(); ++i) {
final BeanPropertyWriter writer = beanProperties.get(i);
Class<?> rawType = writer.getType().getRawClass();
if (supports(rawType)) {
final String suffix = constructSuffix(rawType);
beanProperties.set(i, writer.rename(NameTransformer.simpleTransformer(null, suffix)));
}
}
return beanProperties;
}
private String constructSuffix(Class<?> rawType) {
return "_" + Character.toLowerCase(rawType.getSimpleName().charAt(0));
}
private boolean supports(Class<?> rawClass) {
return rawClass == String.class || rawClass == Long.class;
}
}
Above code prints:
{
"id_s" : "1",
"unitatId_l" : 123,
"grupId_s" : "2",
"descripcio_s" : "3"
}
See also:
Jackson custom serialization and deserialization
Aside from the accepted answer, which works fine, you could also consider implementing PropertyNameStrategy: it would let you rename properties and gets field, setter/getter, creator parameter (which you need to find type of property). Might be little bit less work.

With default typing enabled, is there a way to override the type Jackson outputs?

I am serializing a class that includes an unmodifiable list with default typing enabled. The problem is that the type that Jackson uses is
java.util.Collections$UnmodifiableRandomAccessList
which, for some reason, the deserializer does not know how to handle.
Is there a way to tell Jackson to set the type as
java.util.ArrayList
which the deserializer does know how to handle, instead? If possible, I'd like to do it using mixins.
Something like
public abstract class ObjectMixin {
#JsonCreator
public ObjectMixin(
#JsonProperty("id") String id,
#JsonProperty("list") #JsonSerialize(as = ArrayList.class) List<String> list;
) {}
}
which, unfortunately, does not work.
I would like to start from security risk warning which comes from ObjectMapper documentation:
Notes on security: use "default typing" feature (see
enableDefaultTyping()) is a potential security risk, if used with
untrusted content (content generated by untrusted external parties).
If so, you may want to construct a custom TypeResolverBuilder
implementation to limit possible types to instantiate, (using
setDefaultTyping(com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder<?)).
Lets implement custom resolver:
class CollectionsDefaultTypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {
private final Map<String, String> notValid2ValidIds = new HashMap<>();
public CollectionsDefaultTypeResolverBuilder() {
super(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
this._idType = JsonTypeInfo.Id.CLASS;
this._includeAs = JsonTypeInfo.As.PROPERTY;
notValid2ValidIds.put("java.util.Collections$UnmodifiableRandomAccessList", ArrayList.class.getName());
// add more here...
}
#Override
protected TypeIdResolver idResolver(MapperConfig<?> config, JavaType baseType, Collection<NamedType> subtypes,
boolean forSer, boolean forDeser) {
return new ClassNameIdResolver(baseType, config.getTypeFactory()) {
#Override
protected String _idFrom(Object value, Class<?> cls, TypeFactory typeFactory) {
String id = notValid2ValidIds.get(cls.getName());
if (id != null) {
return id;
}
return super._idFrom(value, cls, typeFactory);
}
};
}
}
Now, we can use it as below:
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver;
import com.fasterxml.jackson.databind.type.TypeFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JsonApp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.setDefaultTyping(new CollectionsDefaultTypeResolverBuilder());
Root root = new Root();
root.setData(Collections.unmodifiableList(Arrays.asList("1", "b")));
String json = mapper.writeValueAsString(root);
System.out.println(json);
System.out.println(mapper.readValue(json, Root.class));
}
}
class Root {
private List<String> data;
public List<String> getData() {
return data;
}
public void setData(List<String> data) {
this.data = data;
}
#Override
public String toString() {
return "Root{" +
"data=" + data +
'}';
}
}
Above code prints:
{
"data" : [ "java.util.ArrayList", [ "1", "b" ] ]
}
Root{data=[1, b]}
You can even map it to List interface:
notValid2ValidIds.put("java.util.Collections$UnmodifiableRandomAccessList", List.class.getName());
And output would be:
{
"data" : [ "java.util.List", [ "1", "b" ] ]
}

Mask json fields using jackson

I am trying to mask sensitive data while serializing using jackson.
I have tried using #JsonSerialize and a custom annotation #Mask .
Mask.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface Mask {
String value() default "XXX-DEFAULT MASK FORMAT-XXX";
}
Employee.java
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.Map;
public class Employee {
#Mask(value = "*** The value of this attribute is masked for security reason ***")
#JsonSerialize(using = MaskStringValueSerializer.class)
protected String name;
#Mask
#JsonSerialize(using = MaskStringValueSerializer.class)
protected String empId;
#JsonSerialize(using = MaskMapStringValueSerializer.class)
protected Map<Category, String> categoryMap;
public Employee() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = empId;
}
public Map<Category, String> getCategoryMap() {
return categoryMap;
}
public void setCategoryMap(Map<Category, String> categoryMap) {
this.categoryMap = categoryMap;
}
}
Category.java
public enum Category {
#Mask
CATEGORY1,
#Mask(value = "*** This value of this attribute is masked for security reason ***")
CATEGORY2,
CATEGORY3;
}
MaskMapStringValueSerializer.java
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.util.Map;
public class MaskMapStringValueSerializer extends JsonSerializer<Map<Category, String>> {
#Override
public void serialize(Map<Category, String> map, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
for (Category key : map.keySet()) {
Mask annot = null;
try {
annot = key.getClass().getField(key.name()).getAnnotation(Mask.class);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
if (annot != null) {
jsonGenerator.writeStringField(((Category) key).name(), annot.value());
} else {
jsonGenerator.writeObjectField(((Category) key).name(), map.get(key));
}
}
jsonGenerator.writeEndObject();
}
}
MaskStringValueSerializer.java
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
public class MaskStringValueSerializer extends StdSerializer<String> implements ContextualSerializer {
private Mask annot;
public MaskStringValueSerializer() {
super(String.class);
}
public MaskStringValueSerializer(Mask logMaskAnnotation) {
super(String.class);
this.annot = logMaskAnnotation;
}
public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (annot != null && s != null && !s.isEmpty()) {
jsonGenerator.writeString(annot.value());
} else {
jsonGenerator.writeString(s);
}
}
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
Mask annot = null;
if (beanProperty != null) {
annot = beanProperty.getAnnotation(Mask.class);
}
return new MaskStringValueSerializer(annot);
}
}
MaskValueTest.java
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
public class MaskValueTest {
public static void main(String args[]) throws Exception{
Employee employee = new Employee();
employee.setName("John Doe");
employee.setEmpId("1234567890");
Map<Category, String> catMap = new HashMap<>();
catMap.put(Category.CATEGORY1, "CATEGORY1");
catMap.put(Category.CATEGORY2, "CATEGORY2");
catMap.put(Category.CATEGORY3, "CATEGORY3");
employee.setCategoryMap(catMap);
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(employee));
}
}
Output -
{
"name" : "*** The value of this attribute is masked for security reason ***",
"empId" : "XXX-DEFAULT MASK FORMAT-XXX",
"categoryMap" : {
"CATEGORY1" : "XXX-DEFAULT MASK FORMAT-XXX",
"CATEGORY2" : "*** The value of this attribute is masked for security reason ***",
"CATEGORY3" : "CATEGORY3"
}
}
The result is as per expectation, however, this seems to be static masking.
The intention was to mask only when needed, e.g. while printing in the logs where the all these sensitive data should be masked.
If I have to send this json for document indexing where the values should be as it is, this implementation fails.
I am looking for an Annotation based solution, where I can use 2 different instance of ObjectMapper initialized with JsonSerializers.
This can be an implementation for what Andreas suggested:
create a class MaskAnnotationIntrospector which extend from JacksonAnnotationIntrospector and override its findSerializer method, like this:
public class MaskAnnotationIntrospector extends JacksonAnnotationIntrospector {
#Override
public Object findSerializer(Annotated am) {
Mask annotation = am.getAnnotation(Mask.class);
if (annotation != null)
return MaskingSerializer.class;
return super.findSerializer(am);
}
}
Therefore, you can have two instance of ObjectMapper. Add MaskAnnotationIntrospector to the one in which you want to Mask (e.g. for logging purpose):
mapper.setAnnotationIntrospector(new MaskAnnotationIntrospector());
The other instance which MaskAnnotationIntrospector has not set into it, do not mask any during serialization.
P.S. MaskAnnotationIntrospector can be extended from both JacksonAnnotationIntrospector & NopAnnotationIntrospector, but the latter does not provide any implementation for findSerializer method and calling super.findSerializer(am) simply return null and as a direct result, other Jackson annotation (such as #JsonIgnore) discarded, but by using the former, this problem solved
Remove the #JsonSerialize annotations, and put the logic of how to handle the #Mask annotation in a Module, e.g. have it add an AnnotationIntrospector.
You can now choose whether or not to call registerModule(Module module).
As for writing the module, I'll leave that up to you. If you have any questions about that, ask another Question.
Instead of having MaskStringValueSerializer.java you can create module to bundle the serializer and register the module with objectmapper whenever you want , which will eventually allow you to have two different instances of objectmapper.
Create a module to bundle the serializer
public class MaskingModule extends SimpleModule {
private static final String NAME = "CustomIntervalModule";
private static final VersionUtil VERSION_UTIL = new VersionUtil() {};
public MaskingModule() {
super(NAME, VERSION_UTIL.version());
addSerializer(MyBean.class, new MaskMapStringValueSerializer());
}
}
Register the module with ObjectMapper and use it
ObjectMapper objectMapper = new ObjectMapper().registerModule(new MaskingModule());
System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(employee));
Also you can extend the Object Mapper , register the module and use it
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
registerModule(new MaskingModule());
}
}
CustomObjectMapper customObjectMapper = new CustomObjectMapper ();
System.out.println(customObjectMapper .writerWithDefaultPrettyPrinter().writeValueAsString(employee));
why don't you use two parameters one for original value and one for masked value. For example in this case you can use String name and String maskedName. then for logging you can use masked value.

Infinite recursion when serializing objects with Jackson and Mockito

I ran into this issue when testing a Spring controller using MockMvc, Mockito and Jackson, so I made a simple class to test out how Jackson behaves. I'm using jackson-databind:2.3.1 and mockito-core:1.9.5.
Given this class:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.Serializable;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class Person implements Serializable {
private String name;
private int age;
// Public getters and setters...
public static void main(String[] args) {
String name = "Bob";
int age = 21;
ObjectMapper objectMapper = new ObjectMapper();
// attempt serialization with real object
Person person = new Person();
person.setName(name);
person.setAge(age);
try {
System.out.println(objectMapper.writeValueAsString(person));
} catch (JsonProcessingException e) {
e.printStackTrace();
System.err.println("Failed to serialize real object");
}
// attempt serialization with mock object
Person mockPerson = mock(Person.class);
when(mockPerson.getName()).thenReturn(name);
when(mockPerson.getAge()).thenReturn(age);
try {
System.out.println(objectMapper.writeValueAsString(mockPerson));
} catch (JsonProcessingException e) {
e.printStackTrace();
System.err.println("Failed to serialize mock object.");
}
}
Jackson has no problem serializing the real object, however it will throw a JsonMappingException when it tries to serialize the mocked object. Debugging through the code, it's calling serializeFields(bean, jgen, provider) repeatedly, getting stuck on the internal Mockito properties.
So, my question is: Is there anyway to force Jackson to use the getter methods? I tried #JsonIgnoreProperties on the class, #JsonIgnore on the fields, and #JsonProperty on the methods (in different combinations, to no success). Or, do I have to write my own custom serializer?
Thanks!
Here is a solution that will work for you particular case:
First of all you need to create a PersonMixin since you cannot add the required annotations to the mock.
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
public interface PersonMixin {
#JsonProperty
String getName();
#JsonProperty
Integer getAge();
}
Now, use the object mapper like the in following code and you will get the same result as when you serialize the real object:
Person mockPerson = mock(Person.class);
when(mockPerson.getName()).thenReturn(name);
when(mockPerson.getAge()).thenReturn(age);
objectMapper.addMixInAnnotations(Person.class, PersonMixin.class);
try {
System.out.println(objectMapper.writeValueAsString(mockPerson));
} catch (JsonProcessingException e) {
e.printStackTrace();
System.err.println("Failed to serialize mock object.");
}
Here's my ObjectMapper that sorted it out without the need for mixins.
The mapper ignores all members of that has "Mockito" in somewhere in their names.
This solution avoids having a mix-in for each serialized object, or annotating code that may not be accessible.
Running the following test succeeds with the output {"name":"Jonh"}.
package test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import org.mockito.Mockito;
public class AppTest extends Mockito {
public void testApp() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
#Override
public boolean hasIgnoreMarker(final AnnotatedMember m) {
return super.hasIgnoreMarker(m) || m.getName().contains("Mockito");
}
});
final String name = "Jonh";
Person mockPerson = mock(Person.class);
when(mockPerson.getName()).thenReturn(name);
System.out.println(mapper.writeValueAsString(mockPerson));
}
public static class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

jackson Pascal Case and #JsonProperty on getter

i create a mapper with
new ObjectMApper()
.setPropertyNamingStrategy(PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE)
.setSerializationInclusion(Include.NON_NULL)
and serialization works perfectly on fields (no getters and setters). field currentStatus is serialized to "currentStatus" (first letter uppercase). but i have also one getter (without a field and setter) which must be camelCase. so i do:
#JsonProperty("abcDef")
public String getZxy() {...
but it is serialized to "AbcDef" instead of "abcDef". it looks like naming strategy still triggers and change the first letter. i use jackson-databind 2.3.2;
how can i map this getter with first letter lowercase?
EDIT:
ugly code, but shows the problem. this test should pass but it fails
import static org.assertj.core.api.Assertions.assertThat;
import org.testng.annotations.Test;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
public class JsonFailureTest {
#Test
public void should_serialize_first_letter_lowercase() throws Exception {
String json = new ObjectMapper()
.setPropertyNamingStrategy(PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE)
.writeValueAsString(
new Object(){
#JsonProperty("fooBar")
public String whatever() {return "";}
});
assertThat(json).contains("fooBar");
}
}
Here's a workaround using a custom "annotation-aware" strategy:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
public class Foo {
public static void main(final String[] args) throws JsonProcessingException {
final SomeObject someObject = new SomeObject();
someObject.setZxy("foobar");
final ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.PascalCaseStrategy() {
#Override
public String nameForGetterMethod(final MapperConfig<?> config, final AnnotatedMethod method, final String defaultName) {
final JsonProperty annotation = method.getAnnotation(JsonProperty.class);
if (annotation != null) {
return annotation.value();
}
return super.nameForGetterMethod(config, method, defaultName);
}
});
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
System.out.println(mapper.writeValueAsString(someObject));
}
private static class SomeObject {
private String zxy;
#JsonProperty("abcDef")
public String getZxy() {
return this.zxy;
}
public void setZxy(final String zxy) {
this.zxy = zxy;
}
}
}
Output:
{"abcDef":"foobar"}

Categories