Jackson - How to convert complex JSON to object from map? - java

The following code might look complex but is actually simple (and is copy/paste compatible):
import java.io.IOException;
import java.util.Map;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonTest {
#Test
public void test_complex_json_to_object() throws JsonGenerationException, JsonMappingException, IOException {
final String json = "{\"source[longitude]\":[\"26.3843435\"],\"source[latitude]\":[\"174.78830093\"]}";
final ObjectMapper m = new ObjectMapper();
#SuppressWarnings("unchecked")
final Map<String, Object> json_map = m.readValue(json, Map.class);
m.convertValue(json_map, Data.class);
}
public static class Location {
public double latitude;
public double longitude;
}
public static class Data {
public Location location;
#JsonProperty(value = "source[longitude]")
public void setSource__longitude(final String[] data) { // Invoked.
System.err.println("longitude");
}
#JsonProperty(value = "source[latitude]")
public void setSource__latitude(final String[] data) { // Invoked.
System.err.println("latitude");
}
public void setLocation(final String[] data) { // NOT Invoked!
System.err.println("latitude");
}
}
}
The JSON string is fixed. I get that out of an external service as a map, thus the conversion to Map before converting to the Data object.
I'd like to have the latitude and longitude setters defined within the Location object, and not as I'm currently forced to (within Data).
Problem is, Jackson doesn't understand that source[longitude] is actually source.longitude.
Any way to solve it?

You need to do custom handling; either by writing custom JsonDeserializer. Your use case is rather esoteric, so there isn't out-of-box support.
It's kind of like having your own data format living inside JSON, so you need to your own handlers.

Related

Can A Data Field in JSON format be parsed? [duplicate]

I am trying to include raw JSON inside a Java object when the object is (de)serialized using Jackson. In order to test this functionality, I wrote the following test:
public static class Pojo {
public String foo;
#JsonRawValue
public String bar;
}
#Test
public void test() throws JsonGenerationException, JsonMappingException, IOException {
String foo = "one";
String bar = "{\"A\":false}";
Pojo pojo = new Pojo();
pojo.foo = foo;
pojo.bar = bar;
String json = "{\"foo\":\"" + foo + "\",\"bar\":" + bar + "}";
ObjectMapper objectMapper = new ObjectMapper();
String output = objectMapper.writeValueAsString(pojo);
System.out.println(output);
assertEquals(json, output);
Pojo deserialized = objectMapper.readValue(output, Pojo.class);
assertEquals(foo, deserialized.foo);
assertEquals(bar, deserialized.bar);
}
The code outputs the following line:
{"foo":"one","bar":{"A":false}}
The JSON is exactly how I want things to look. Unfortunately, the code fails with an exception when attempting to read the JSON back in to the object. Here is the exception:
org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: java.io.StringReader#d70d7a; line: 1, column: 13] (through reference chain: com.tnal.prism.cobalt.gather.testing.Pojo["bar"])
Why does Jackson function just fine in one direction but fail when going the other direction? It seems like it should be able to take its own output as input again. I know what I'm trying to do is unorthodox (the general advice is to create an inner object for bar that has a property named A), but I don't want to interact with this JSON at all. My code is acting as a pass-through for this code -- I want to take in this JSON and send it back out again without touching a thing, because when the JSON changes I don't want my code to need modifications.
Thanks for the advice.
EDIT: Made Pojo a static class, which was causing a different error.
#JsonRawValue is intended for serialization-side only, since the reverse direction is a bit trickier to handle. In effect it was added to allow injecting pre-encoded content.
I guess it would be possible to add support for reverse, although that would be quite awkward: content will have to be parsed, and then re-written back to "raw" form, which may or may not be the same (since character quoting may differ).
This for general case. But perhaps it would make sense for some subset of problems.
But I think a work-around for your specific case would be to specify type as 'java.lang.Object', since this should work ok: for serialization, String will be output as is, and for deserialization, it will be deserialized as a Map. Actually you might want to have separate getter/setter if so; getter would return String for serialization (and needs #JsonRawValue); and setter would take either Map or Object. You could re-encode it to a String if that makes sense.
Following #StaxMan answer, I've made the following works like a charm:
public class Pojo {
Object json;
#JsonRawValue
public String getJson() {
// default raw value: null or "[]"
return json == null ? null : json.toString();
}
public void setJson(JsonNode node) {
this.json = node;
}
}
And, to be faithful to the initial question, here is the working test:
public class PojoTest {
ObjectMapper mapper = new ObjectMapper();
#Test
public void test() throws IOException {
Pojo pojo = new Pojo("{\"foo\":18}");
String output = mapper.writeValueAsString(pojo);
assertThat(output).isEqualTo("{\"json\":{\"foo\":18}}");
Pojo deserialized = mapper.readValue(output, Pojo.class);
assertThat(deserialized.json.toString()).isEqualTo("{\"foo\":18}");
// deserialized.json == {"foo":18}
}
}
I was able to do this with a custom deserializer (cut and pasted from here)
package etc;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
/**
* Keeps json value as json, does not try to deserialize it
* #author roytruelove
*
*/
public class KeepAsJsonDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
TreeNode tree = jp.getCodec().readTree(jp);
return tree.toString();
}
}
Use it by annotating the desired member like this:
#JsonDeserialize(using = KeepAsJsonDeserializer.class)
private String value;
#JsonSetter may help. See my sample ('data' is supposed to contain unparsed JSON):
class Purchase
{
String data;
#JsonProperty("signature")
String signature;
#JsonSetter("data")
void setData(JsonNode data)
{
this.data = data.toString();
}
}
This is a problem with your inner classes. The Pojo class is a non-static inner class of your test class, and Jackson cannot instantiate that class. So it can serialize, but not deserialize.
Redefine your class like this:
public static class Pojo {
public String foo;
#JsonRawValue
public String bar;
}
Note the addition of static
Adding to Roy Truelove's great answer, this is how to inject the custom deserialiser in response to appearance of #JsonRawValue:
import com.fasterxml.jackson.databind.Module;
#Component
public class ModuleImpl extends Module {
#Override
public void setupModule(SetupContext context) {
context.addBeanDeserializerModifier(new BeanDeserializerModifierImpl());
}
}
import java.util.Iterator;
import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
public class BeanDeserializerModifierImpl extends BeanDeserializerModifier {
#Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
Iterator<SettableBeanProperty> it = builder.getProperties();
while (it.hasNext()) {
SettableBeanProperty p = it.next();
if (p.getAnnotation(JsonRawValue.class) != null) {
builder.addOrReplaceProperty(p.withValueDeserializer(KeepAsJsonDeserialzier.INSTANCE), true);
}
}
return builder;
}
}
This easy solution worked for me:
public class MyObject {
private Object rawJsonValue;
public Object getRawJsonValue() {
return rawJsonValue;
}
public void setRawJsonValue(Object rawJsonValue) {
this.rawJsonValue = rawJsonValue;
}
}
So I was able to store raw value of JSON in rawJsonValue variable and then it was no problem to deserialize it (as object) with other fields back to JSON and send via my REST. Using #JsonRawValue didnt helped me because stored JSON was deserialized as String, not as object, and that was not what I wanted.
This even works in a JPA entity:
private String json;
#JsonRawValue
public String getJson() {
return json;
}
public void setJson(final String json) {
this.json = json;
}
#JsonProperty(value = "json")
public void setJsonRaw(JsonNode jsonNode) {
// this leads to non-standard json, see discussion:
// setJson(jsonNode.toString());
StringWriter stringWriter = new StringWriter();
ObjectMapper objectMapper = new ObjectMapper();
JsonGenerator generator =
new JsonFactory(objectMapper).createGenerator(stringWriter);
generator.writeTree(n);
setJson(stringWriter.toString());
}
Ideally the ObjectMapper and even JsonFactory are from the context and are configured so as to handle your JSON correctly (standard or with non-standard values like 'Infinity' floats for example).
Here is a full working example of how to use Jackson modules to make #JsonRawValue work both ways (serialization and deserialization):
public class JsonRawValueDeserializerModule extends SimpleModule {
public JsonRawValueDeserializerModule() {
setDeserializerModifier(new JsonRawValueDeserializerModifier());
}
private static class JsonRawValueDeserializerModifier extends BeanDeserializerModifier {
#Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
builder.getProperties().forEachRemaining(property -> {
if (property.getAnnotation(JsonRawValue.class) != null) {
builder.addOrReplaceProperty(property.withValueDeserializer(JsonRawValueDeserializer.INSTANCE), true);
}
});
return builder;
}
}
private static class JsonRawValueDeserializer extends JsonDeserializer<String> {
private static final JsonDeserializer<String> INSTANCE = new JsonRawValueDeserializer();
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return p.readValueAsTree().toString();
}
}
}
Then you can register the module after creating the ObjectMapper:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JsonRawValueDeserializerModule());
String json = "{\"foo\":\"one\",\"bar\":{\"A\":false}}";
Pojo deserialized = objectMapper.readValue(json, Pojo.class);
I had the exact same issue.
I found the solution in this post :
Parse JSON tree to plain class using Jackson or its alternatives
Check out the last answer.
By defining a custom setter for the property that takes a JsonNode as parameter and calls the toString method on the jsonNode to set the String property, it all works out.
Using an object works fine both ways... This method has a bit of overhead deserializing the raw value in two times.
ObjectMapper mapper = new ObjectMapper();
RawJsonValue value = new RawJsonValue();
value.setRawValue(new RawHello(){{this.data = "universe...";}});
String json = mapper.writeValueAsString(value);
System.out.println(json);
RawJsonValue result = mapper.readValue(json, RawJsonValue.class);
json = mapper.writeValueAsString(result.getRawValue());
System.out.println(json);
RawHello hello = mapper.readValue(json, RawHello.class);
System.out.println(hello.data);
RawHello.java
public class RawHello {
public String data;
}
RawJsonValue.java
public class RawJsonValue {
private Object rawValue;
public Object getRawValue() {
return rawValue;
}
public void setRawValue(Object value) {
this.rawValue = value;
}
}
I had a similar problem, but using a list with a lot of JSON itens (List<String>).
public class Errors {
private Integer status;
private List<String> jsons;
}
I managed the serialization using the #JsonRawValue annotation. But for deserialization I had to create a custom deserializer based on Roy's suggestion.
public class Errors {
private Integer status;
#JsonRawValue
#JsonDeserialize(using = JsonListPassThroughDeserialzier.class)
private List<String> jsons;
}
Below you can see my "List" deserializer.
public class JsonListPassThroughDeserializer extends JsonDeserializer<List<String>> {
#Override
public List<String> deserialize(JsonParser jp, DeserializationContext cxt) throws IOException, JsonProcessingException {
if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
final List<String> list = new ArrayList<>();
while (jp.nextToken() != JsonToken.END_ARRAY) {
list.add(jp.getCodec().readTree(jp).toString());
}
return list;
}
throw cxt.instantiationException(List.class, "Expected Json list");
}
}

environment.getArgument can't cast to my own java object in graphql-java

I Can't cast input object to DTO because of below error ExecutionStrategy.resolveField() - Exception while fetching data java.lang.ClassCastException: java.util.LinkedHashMap incompatible with com.fathome.graphql.OffersDto
OffersDto inputObject = environment.getArgument("offersInput");
please let me know what's wrong in below code, thanks in advance.
package com.fathome.graphql;
import graphql.schema.*;
import static graphql.Scalars.*;
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
import static graphql.schema.GraphQLInputObjectField.newInputObjectField;
import static graphql.schema.GraphQLInputObjectType.newInputObject;
import static graphql.schema.GraphQLList.list;
import static graphql.schema.GraphQLObjectType.newObject;
public class ManualGraphQLQuerySchema {
public static GraphQLObjectType offersResponse = newObject()
.name("OffersResponse")
.field(newFieldDefinition()
.name("offerName")
.type(GraphQLString))
.field(newFieldDefinition()
.name("offerId")
.type(GraphQLString))
.build();
public static GraphQLInputObjectType offersRequestType = GraphQLInputObjectType.newInputObject()
.name("OffersDto")
.field(newInputObjectField()
.name("offerName")
.type(GraphQLString))
.field(newInputObjectField()
.name("offerId")
.type(GraphQLString))
.build();
public static GraphQLObjectType queryType = newObject()
.name("QueryType")
.field(newFieldDefinition()
.name("offers")
.type(offersResponse)
.argument(GraphQLArgument.newArgument()
.name("offersInput")
.type(offersRequestType))
.dataFetcher(new OffersFetcher()))
.build();
}
package com.fathome.graphql;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"offerName",
"offerId"
})
public class OffersDto {
#JsonProperty("offerName")
private String offerName;
#JsonProperty("offerName")
public String getOfferName() {
return offerName;
}
#JsonProperty("offerName")
public void setOfferName(String offerName) {
this.offerName = offerName;
}
#JsonProperty("offerId")
private String offerId;
#JsonProperty("offerId")
public String getOfferId() {
return offerId;
}
#JsonProperty("offerId")
public void setOfferId(String offerId) {
this.offerId = offerId;
}
}
package com.fathome.graphql;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
public class OffersFetcher implements DataFetcher<OffersDto> {
#Override
public OffersDto get(DataFetchingEnvironment environment) {
//Can't cast input object DTO this is error in below line
//ExecutionStrategy.resolveField() - Exception while fetching data
//java.lang.ClassCastException: java.util.LinkedHashMap incompatible with com.fathome.graphql.OffersDto
OffersDto inputObject = environment.getArgument("offersInput");
//calling service to get offerdetails using inputObject
//for testing not calling service just returning mock object.
OffersDto offersDto = new OffersDto();
offersDto.setOfferName("123");
offersDto.setOfferId("456");
return offersDto;
}
}
In below reference link similar to my code working fine.
Episode episode = environment.getArgument("episode");
ReviewInput review = environment.getArgument("review");
http://graphql-java.readthedocs.io/en/latest/execution.html
The values you get from environment.getArgument(...) will either be scalar values (strings, numbers etc) or a Map in case of an object GraphQL input type (like in your case).
You then need to do the deserialization yourself. Since you're using Jackson, it would look like this:
private final ObjectMapper objectMapper;
...
Object rawInput = environment.getArgument("offersInput");
OffersDto inputObject = objectMapper.convertValue(rawInput, OffersDto.class);
Check out graphql-java-tools for schema-first approach, or graphql-spqr for code-first, both make DataFetchers completely transparent, so no manual steps like above needed.

Check if json property is present, is string, and is correct value

Using gson, I use this cumbersome approach to make sure a required property has a desired value:
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class ScratchSpace {
public static void main(String[] args) {
// create json object from source data - in my real code, this is sourced externally
JsonObject json = new JsonParser().parse("{ \"product\": \"foobar\"}").getAsJsonObject();
// does this object have a key called product, which is a string, and equal to our expected value?
boolean correctProduct = false;
if (json.has("product")) {
JsonElement productElement = json.get("product");
if (productElement.isJsonPrimitive()) {
String product = productElement.getAsString();
if ("foobar".equals(product)) {
correctProduct = true;
}
}
}
System.out.println("correctProduct = " + correctProduct);
}
}
I'm almost certain I'm doing this suboptimally. Is there a simple, readable, short-ish one-liner to achieve the same?
Edit: if possible, I'd like to keep using gson.
Using java.util.Optional, the following works:
final boolean correctProduct = Optional.ofNullable(json.get("product"))
.filter(JsonPrimitive.class::isInstance)
.map(JsonPrimitive.class::cast)
.map(JsonPrimitive::getAsString)
.filter("foobar"::equals)
.isPresent();
You can write a custom deserializer like this, register it, and use fromJson method to obtain object directly from json string. In this way, you can return null or throw exception in deserializer if the json string is not in expected format.
Note that you don't have to set each field seperately. After performing custom checks, you can use default deserialization from context.
EDIT: If you want to obtain just true/false instead of the complete object, then you can write a MyBoolean class, holding a simple boolean value and use fromJson method to deserialize to MyBoolean class. The custom deserializer will perform only the desired checks and set the content of MyBoolean instance appropriately. Furthermore, (I guess) if you extend this MyBoolean from Boolean, you can use it as boolean too.
EDIT 2: I didn't have to time to include a sample code before. Here is what I suggest:
package io.ram.ram;
import java.lang.reflect.Type;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
public class Tester {
public static class MyBoolean {
private boolean value;
public void set(boolean value) {
this.value = value;
}
public boolean get() {
return value;
}
}
public static class MyAdapter implements JsonDeserializer<MyBoolean> {
public MyBoolean deserialize(JsonElement json, Type type, JsonDeserializationContext context)
throws JsonParseException {
MyBoolean result = new MyBoolean();
result.set(false);
try {
result.set(json.getAsJsonObject().get("product").getAsString().equals("foobar"));
} catch (Exception e) {
}
return result;
}
}
public static void main(String[] args) {
Gson gson = new GsonBuilder().registerTypeAdapter(MyBoolean.class, new MyAdapter()).create();
System.out.println(gson.fromJson("{\"product\": \"foobar\"}", MyBoolean.class).get());
System.out.println(gson.fromJson("{\"product\": \"foobaz\"}", MyBoolean.class).get());
}
}
As I said, after you register a type adapter for custom serialization, you can achieve what you want with a single line. (Note: Boolean class is final, so we cannot extend it. Sorry for this wrong information.)
You can parametrize MyAdapter for strings "product" and "foobar" of course, thus you don't have to create such classes for every possible cases.
I know you said GSON, but the pojo based approach jackson offers makes what you want to do just too convenient to not to post:
Simple pojo:
public class FooPojo {
#JsonProperty
private String product;
public String getProduct() {
return product;
}
public void setProduct(String product) {
this.product = product;
}
public boolean isProductEqualTo(String check) {
return product.equals(check);
}
}
Parse and check:
public static void main(String[] args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
FooPojo fooPojo = objectMapper.readValue("{ \"product\": \"foobar\"}", FooPojo.class);
System.out.println(fooPojo.isProductEqualTo("foobar"));
}

Jackson map different attributes based on json in the same class

I have a class Response which has an attribute data.
A json file is mapped to this object. The data attribute can be of type TaskData or SubmitData on the json.
If the json has the object of type TaskData the object mapper must map to TaskData class or should map to `SubmitData' class.
In addition to the answer by Guillaume Polet, and if you can modify the JSON schema, this can also be done a little bit smoother using Jackson's Polymorphic (de)serialization via annotations:
#JsonTypeInfo(use=JsonTypeInfo.Id.Class, include=JsonTypeInfo.As.PROPERTY, property="#class")
class Data {}
class TaskData extends Data {}
class SubmitData extends Data {}
This will write out the full Java class name as an additional #class property. The json needs to include the #class property on input, however.
Instead of JsonTypeInfo.Id.Class it is also possible to perform explicit naming
#JsonTypeInfo(use=JsonTypeInfo.Id.Class, include=JsonTypeInfo.As.PROPERTY, property="#dataType")
#JsonSubTypes({
JsonSubTypes.Type(value=TaskData.class, name="task"),
JsonSubTypes.Type(value=SubmitData.class, name="submit")
})
class Data {}
#JsonTypeName("task")
class TaskData extends Data {}
#JsonTypeName("submit")
class SubmitData extends Data {}
This will yield an additional synthetic field #dataType, which will need to be present in the input.
If you cannot make the type explicit in the input JSON, you will need to stick to the manual approach.
You need to type your Response class as follows: public class Response<T>.
Then, when deserializing the input, provide a TypeReference to jackson to indicate the desired type.
See this example:
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TestJacksonTyping {
public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
Response<TaskData> taskResponse = new Response<TaskData>();
TaskData taskData = new TaskData();
taskData.setTaskTitle("Some title");
taskResponse.setData(taskData);
Response<SubmitData> submitResponse = new Response<SubmitData>();
SubmitData submitData = new SubmitData();
submitData.setSubmitValue(256);
submitResponse.setData(submitData);
StringWriter sw = new StringWriter();
mapper.writeValue(sw, taskResponse);
String taskResponseJson = sw.toString();
mapper.writeValue(sw = new StringWriter(), submitResponse);
String submitResponseJson = sw.toString();
Response<TaskData> deserializedTaskResponse = mapper.reader(new TypeReference<Response<TaskData>>() {
}).readValue(new StringReader(taskResponseJson));
Response<SubmitData> deserializedSubmitResponse = mapper.reader(new TypeReference<Response<SubmitData>>() {
}).readValue(new StringReader(submitResponseJson));
System.out.println(deserializedTaskResponse.getData().getTaskTitle());
System.out.println(deserializedSubmitResponse.getData().getSubmitValue());
}
public static class Response<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
public static class TaskData {
private String taskTitle;
public String getTaskTitle() {
return taskTitle;
}
public void setTaskTitle(String taskTitle) {
this.taskTitle = taskTitle;
}
}
public static class SubmitData {
private int submitValue;
public int getSubmitValue() {
return submitValue;
}
public void setSubmitValue(int submitValue) {
this.submitValue = submitValue;
}
}
}

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