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");
}
}
Related
How can I deserialize JSON string that contains enum values that are case insensitive? (using Jackson Databind)
The JSON string:
[{"url": "foo", "type": "json"}]
and my Java POJO:
public static class Endpoint {
public enum DataType {
JSON, HTML
}
public String url;
public DataType type;
public Endpoint() {
}
}
in this case,deserializing the JSON with "type":"json" would fail where as "type":"JSON" would work.
But I want "json" to work as well for naming convention reasons.
Serializing the POJO also results in upper case "type":"JSON"
I thought of using #JsonCreator and #JsonGetter:
#JsonCreator
private Endpoint(#JsonProperty("name") String url, #JsonProperty("type") String type) {
this.url = url;
this.type = DataType.valueOf(type.toUpperCase());
}
//....
#JsonGetter
private String getType() {
return type.name().toLowerCase();
}
And it worked. But I was wondering whether there's a better solutuon because this looks like a hack to me.
I can also write a custom deserializer but I got many different POJOs that use enums and it would be hard to maintain.
Can anyone suggest a better way to serialize and deserialize enums with proper naming convention?
I don't want my enums in java to be lowercase!
Here is some test code that I used:
String data = "[{\"url\":\"foo\", \"type\":\"json\"}]";
Endpoint[] arr = new ObjectMapper().readValue(data, Endpoint[].class);
System.out.println("POJO[]->" + Arrays.toString(arr));
System.out.println("JSON ->" + new ObjectMapper().writeValueAsString(arr));
Jackson 2.9
This is now very simple, using jackson-databind 2.9.0 and above
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
// objectMapper now deserializes enums in a case-insensitive manner
Full example with tests
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
private enum TestEnum { ONE }
private static class TestObject { public TestEnum testEnum; }
public static void main (String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
try {
TestObject uppercase =
objectMapper.readValue("{ \"testEnum\": \"ONE\" }", TestObject.class);
TestObject lowercase =
objectMapper.readValue("{ \"testEnum\": \"one\" }", TestObject.class);
TestObject mixedcase =
objectMapper.readValue("{ \"testEnum\": \"oNe\" }", TestObject.class);
if (uppercase.testEnum != TestEnum.ONE) throw new Exception("cannot deserialize uppercase value");
if (lowercase.testEnum != TestEnum.ONE) throw new Exception("cannot deserialize lowercase value");
if (mixedcase.testEnum != TestEnum.ONE) throw new Exception("cannot deserialize mixedcase value");
System.out.println("Success: all deserializations worked");
} catch (Exception e) {
e.printStackTrace();
}
}
}
I ran into this same issue in my project, we decided to build our enums with a string key and use #JsonValue and a static constructor for serialization and deserialization respectively.
public enum DataType {
JSON("json"),
HTML("html");
private String key;
DataType(String key) {
this.key = key;
}
#JsonCreator
public static DataType fromString(String key) {
return key == null
? null
: DataType.valueOf(key.toUpperCase());
}
#JsonValue
public String getKey() {
return key;
}
}
Since Jackson 2.6, you can simply do this:
public enum DataType {
#JsonProperty("json")
JSON,
#JsonProperty("html")
HTML
}
For a full example, see this gist.
In version 2.4.0 you can register a custom serializer for all the Enum types (link to the github issue). Also you can replace the standard Enum deserializer on your own that will be aware about the Enum type. Here is an example:
public class JacksonEnum {
public static enum DataType {
JSON, HTML
}
public static void main(String[] args) throws IOException {
List<DataType> types = Arrays.asList(JSON, HTML);
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<Enum> modifyEnumDeserializer(DeserializationConfig config,
final JavaType type,
BeanDescription beanDesc,
final JsonDeserializer<?> deserializer) {
return new JsonDeserializer<Enum>() {
#Override
public Enum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
Class<? extends Enum> rawClass = (Class<Enum<?>>) type.getRawClass();
return Enum.valueOf(rawClass, jp.getValueAsString().toUpperCase());
}
};
}
});
module.addSerializer(Enum.class, new StdSerializer<Enum>(Enum.class) {
#Override
public void serialize(Enum value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString(value.name().toLowerCase());
}
});
mapper.registerModule(module);
String json = mapper.writeValueAsString(types);
System.out.println(json);
List<DataType> types2 = mapper.readValue(json, new TypeReference<List<DataType>>() {});
System.out.println(types2);
}
}
Output:
["json","html"]
[JSON, HTML]
If you're using Spring Boot 2.1.x with Jackson 2.9 you can simply use this application property:
spring.jackson.mapper.accept-case-insensitive-enums=true
I went for the solution of Sam B. but a simpler variant.
public enum Type {
PIZZA, APPLE, PEAR, SOUP;
#JsonCreator
public static Type fromString(String key) {
for(Type type : Type.values()) {
if(type.name().equalsIgnoreCase(key)) {
return type;
}
}
return null;
}
}
For those who tries to deserialize Enum ignoring case in GET parameters, enabling ACCEPT_CASE_INSENSITIVE_ENUMS will not do any good. It won't help because this option only works for body deserialization. Instead try this:
public class StringToEnumConverter implements Converter<String, Modes> {
#Override
public Modes convert(String from) {
return Modes.valueOf(from.toUpperCase());
}
}
and then
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEnumConverter());
}
}
The answer and code samples are from here
To allow case insensitive deserialization of enums in jackson, simply add the below property to the application.properties file of your spring boot project.
spring.jackson.mapper.accept-case-insensitive-enums=true
If you have the yaml version of properties file, add below property to your application.yml file.
spring:
jackson:
mapper:
accept-case-insensitive-enums: true
With apologies to #Konstantin Zyubin, his answer was close to what I needed - but I didn't understand it, so here's how I think it should go:
If you want to deserialize one enum type as case insensitive - i.e. you don't want to, or can't, modify the behavior of the entire application, you can create a custom deserializer just for one type - by sub-classing StdConverter and force Jackson to use it only on the relevant fields using the JsonDeserialize annotation.
Example:
public class ColorHolder {
public enum Color {
RED, GREEN, BLUE
}
public static final class ColorParser extends StdConverter<String, Color> {
#Override
public Color convert(String value) {
return Arrays.stream(Color.values())
.filter(e -> e.getName().equalsIgnoreCase(value.trim()))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Invalid value '" + value + "'"));
}
}
#JsonDeserialize(converter = ColorParser.class)
Color color;
}
Problem is releated to com.fasterxml.jackson.databind.util.EnumResolver. it uses HashMap to hold enum values and HashMap doesn't support case insensitive keys.
in answers above, all chars should be uppercase or lowercase. but I fixed all (in)sensitive problems for enums with that:
https://gist.github.com/bhdrk/02307ba8066d26fa1537
CustomDeserializers.java
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.std.EnumDeserializer;
import com.fasterxml.jackson.databind.module.SimpleDeserializers;
import com.fasterxml.jackson.databind.util.EnumResolver;
import java.util.HashMap;
import java.util.Map;
public class CustomDeserializers extends SimpleDeserializers {
#Override
#SuppressWarnings("unchecked")
public JsonDeserializer<?> findEnumDeserializer(Class<?> type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException {
return createDeserializer((Class<Enum>) type);
}
private <T extends Enum<T>> JsonDeserializer<?> createDeserializer(Class<T> enumCls) {
T[] enumValues = enumCls.getEnumConstants();
HashMap<String, T> map = createEnumValuesMap(enumValues);
return new EnumDeserializer(new EnumCaseInsensitiveResolver<T>(enumCls, enumValues, map));
}
private <T extends Enum<T>> HashMap<String, T> createEnumValuesMap(T[] enumValues) {
HashMap<String, T> map = new HashMap<String, T>();
// from last to first, so that in case of duplicate values, first wins
for (int i = enumValues.length; --i >= 0; ) {
T e = enumValues[i];
map.put(e.toString(), e);
}
return map;
}
public static class EnumCaseInsensitiveResolver<T extends Enum<T>> extends EnumResolver<T> {
protected EnumCaseInsensitiveResolver(Class<T> enumClass, T[] enums, HashMap<String, T> map) {
super(enumClass, enums, map);
}
#Override
public T findEnum(String key) {
for (Map.Entry<String, T> entry : _enumsById.entrySet()) {
if (entry.getKey().equalsIgnoreCase(key)) { // magic line <--
return entry.getValue();
}
}
return null;
}
}
}
Usage:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
public class JSON {
public static void main(String[] args) {
SimpleModule enumModule = new SimpleModule();
enumModule.setDeserializers(new CustomDeserializers());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(enumModule);
}
}
I used a modification of Iago Fernández and Paul solution .
I had an enum in my requestobject which needed to be case insensitive
#POST
public Response doSomePostAction(RequestObject object){
//resource implementation
}
class RequestObject{
//other params
MyEnumType myType;
#JsonSetter
public void setMyType(String type){
myType = MyEnumType.valueOf(type.toUpperCase());
}
#JsonGetter
public String getType(){
return myType.toString();//this can change
}
}
Here's how I sometimes handle enums when I want to deserialize in a case-insensitive manner (building on the code posted in the question):
#JsonIgnore
public void setDataType(DataType dataType)
{
type = dataType;
}
#JsonProperty
public void setDataType(String dataType)
{
// Clean up/validate String however you want. I like
// org.apache.commons.lang3.StringUtils.trimToEmpty
String d = StringUtils.trimToEmpty(dataType).toUpperCase();
setDataType(DataType.valueOf(d));
}
If the enum is non-trivial and thus in its own class I usually add a static parse method to handle lowercase Strings.
Deserialize enum with jackson is simple. When you want deserialize enum based in String need a constructor, a getter and a setter to your enum.Also class that use that enum must have a setter which receive DataType as param, not String:
public class Endpoint {
public enum DataType {
JSON("json"), HTML("html");
private String type;
#JsonValue
public String getDataType(){
return type;
}
#JsonSetter
public void setDataType(String t){
type = t.toLowerCase();
}
}
public String url;
public DataType type;
public Endpoint() {
}
public void setType(DataType dataType){
type = dataType;
}
}
When you have your json, you can deserialize to Endpoint class using ObjectMapper of Jackson:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
Endpoint endpoint = mapper.readValue("{\"url\":\"foo\",\"type\":\"json\"}", Endpoint.class);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
I have a request coming like this
{
"varA" : "A",
"varB" : "TCFNhbiBKb3NlMRgwFgYDVQQK"
}
where key varB is a base64 encoded JSON string. something like this:
{
"nestedVarB1": "some value here",
"nestedVarB2" : "some other value here"
}
I want to convert it into some POJOs like this:
#Data
public class MyRequest {
private String varA;
private B varB;
}
#Data
public class B {
private String nestedVarB1;
private String nestedVarB2;
}
I went through several solutions on stack overflow like this and this, but I want to convert the JSON directly into an object of type MyRequest maybe by writing some sort of Jackson deserializer.
How can I convert the JSON directly into MyRequest using Jackson and Spring Boot?
Note:
POJOs MyRequest and B are very big and B could be further nested, so doing it manually would be a big task.
I don't have any control over the request coming, it is coming from a third party source. So, can't change the format of the request.
I've done some experiments and here is a simple Jackson Deserializer which should work for you.
Deserializer implements the ContextualDeserializer interface to get access to actual bean property (for example varB). It's necessary for detecting correct result type, because deserializer itself can be attached to a field of any type.
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import java.io.IOException;
import java.util.Base64;
public class Base64Deserializer extends JsonDeserializer<Object> implements ContextualDeserializer {
private Class<?> resultClass;
#Override
public JsonDeserializer<?> createContextual(DeserializationContext context, BeanProperty property) throws JsonMappingException {
this.resultClass = property.getType().getRawClass();
return this;
}
#Override
public Object deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
String value = parser.getValueAsString();
Base64.Decoder decoder = Base64.getDecoder();
try {
ObjectMapper objectMapper = new ObjectMapper();
byte[] decodedValue = decoder.decode(value);
return objectMapper.readValue(decodedValue, this.resultClass);
} catch (IllegalArgumentException | JsonParseException e) {
String fieldName = parser.getParsingContext().getCurrentName();
Class<?> wrapperClass = parser.getParsingContext().getCurrentValue().getClass();
throw new InvalidFormatException(
parser,
String.format("Value for '%s' is not a base64 encoded JSON", fieldName),
value,
wrapperClass
);
}
}
}
Here is an example of mapped class.
public class MyRequest {
private String varA;
#JsonDeserialize(using = Base64Deserializer.class)
private B varB;
public String getVarA() {
return varA;
}
public void setVarA(String varA) {
this.varA = varA;
}
public B getVarB() {
return varB;
}
public void setVarB(B varB) {
this.varB = varB;
}
}
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"));
}
I am using Jackson fasterxml for unmarshalling JSON. In my object there are two kinds of properties:Input properties and Calculated properties. In the input JSON, I get only input values.
The calculated values are actually dependent on input values. I have to populate these values before the object gets referred. So I am just checking if there are any hooks provided by Jackson so that I can do my calculations there. For example JAXB provides afterUnmarshal method to customize the unmarshaling behavior:
void afterUnmarshal(Unmarshaller u, Object parent)
But I could not find similar information about customizing Jackson. Are any such framework hooks provided by Jackson to customize the unmarshaling behavior?
I'd rather recommend to keep your model objects immutable by using constructor creators. That is, all the JSON values are passed to a constructor which would initialize the other calculated properties.
Anyway, if you want to customize an object after deserialization (without writing a deserializer for every type) you can modify the deserializer in a way that at the end it calls a special method(s) of a newly constructed instance. Here is an example which would work for all the classes that implements a special interface (one can consider using an annotation to mark the post construct methods).
public class JacksonPostConstruct {
public static interface PostConstructor {
void postConstruct();
}
public static class Bean implements PostConstructor {
private final String field;
#JsonCreator
public Bean(#JsonProperty("field") String field) {
this.field = field;
}
public void postConstruct() {
System.out.println("Post construct: " + toString());
}
#Override
public String toString() {
return "Bean{" +
"field='" + field + '\'' +
'}';
}
}
private static class PostConstructDeserializer extends DelegatingDeserializer {
private final JsonDeserializer<?> deserializer;
public PostConstructDeserializer(JsonDeserializer<?> deserializer) {
super(deserializer);
this.deserializer = deserializer;
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return deserializer;
}
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
Object result = _delegatee.deserialize(jp, ctxt);
if (result instanceof PostConstructor) {
((PostConstructor) result).postConstruct();
}
return result;
}
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc,
final JsonDeserializer<?> deserializer) {
return new PostConstructDeserializer(deserializer);
}
});
mapper.registerModule(module);
String json = "{\"field\":\"value\"}";
System.out.println(mapper.readValue(json, Bean.class));
}
}
Output:
Post construct: Bean{field='value'}
Bean{field='value'}
Let's assume that your JSON looks like this:
{
"input1" : "Input value",
"input2" : 3
}
And your POJO class looks like this:
class Entity {
private String input1;
private int input2;
private String calculated1;
private long calculated2;
...
}
In this case you can write a custom deserializer for your Entity class:
class EntityJsonDeserializer extends JsonDeserializer<Entity> {
#Override
public Entity deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
JsonProcessingException {
InnerEntity innerEntity = jp.readValueAs(InnerEntity.class);
Entity entity = new Entity();
entity.setInput1(innerEntity.input1);
entity.setInput2(innerEntity.input2);
entity.recalculate();
return entity;
}
public static class InnerEntity {
public String input1;
public int input2;
}
}
In above class you can see that Entity has a recalculate method. It could look like this:
public void recalculate() {
calculated1 = input1 + input2;
calculated2 = input1.length() + input2;
}
You can also move this logic to your deserializer class.
Now, you have to inform Jackson that you want to use your custom deserializer:
#JsonDeserialize(using = EntityJsonDeserializer.class)
class Entity {
...
}
The example below shows how to use these classes:
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(json, Entity.class));
This program prints:
Entity [input1=Input value, input2=3, calculated1=Input value3, calculated2=14]
I have a web service that returns a list as JSON. It uses Jackson to map a List of Java POJOs into JSON. The problem is that the JSON representation has a wrapper object around the array, and I just want the array. I.e., I'm getting this:
{"optionDtoList":[{...}, ..., {...}]}
when what I really want is this:
[{...}, ..., {...}]
I am serializing the Java List directly; I'm not wrapping the List with a wrapper object and serializing a wrapper object. It's Jackson that seems to be adding the JavaScript wrapper object.
I assume there's some annotation I can use on the POJO to suppress the wrapper object but I'm not seeing it.
Constraints on solution
I'd like to fix this on the service side rather than peeling off the wrapper on the client. The client is a jQuery UI widget (the autocomplete widget, not that it matters) that expects a simple array and I don't want to modify the widget itself.
What I've tried
I tried replacing the List of Java POJOs with an array of Java POJOs and the result is the same.
I tried #JsonTypeInfo(use = Id.NONE) thinking that that might suppress the wrapper, but it didn't.
In a test mode when I run:
org.codehaus.jackson.map.ObjectMapper mapper = new org.codehaus.jackson.map.ObjectMapper();
String json = mapper.writeValueAsString( Arrays.asList("one","two","three","four","five") );
System.out.println(json);
returns:
["one","two","three","four","five"]
which is the behavior you are expecting right?
I could see that when I return this list via a Spring controller and let MappingJacksonJsonView handle transforming the list to a json, then yes there is a wrapper around it, which tells me that the MappingJacksonJsonView is the one adding the wrapper. One solution then would be to explicitly return the json from your controller, say:
#RequestMapping(value = "/listnowrapper")
public #ResponseBody String listNoWrapper() throws Exception{
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(Arrays.asList("one","two","three","four","five"));
}
I get the same problem as you.
After add #ResponseBody in front of my list in my method declaration, the problem was solved.
eg :
public #ResponseBody List<MyObject> getObject
You could write custom serializer:
public class UnwrappingSerializer extends JsonSerializer<Object>
{
#Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException
{
JavaType type = TypeFactory.type(value.getClass());
getBeanSerializer(type, provider).serialize(value, new UnwrappingJsonGenerator(jgen), provider);
}
private synchronized JsonSerializer<Object> getBeanSerializer(JavaType type, SerializerProvider provider)
{
JsonSerializer<Object> result = cache.get(type);
if (result == null) {
BasicBeanDescription beanDesc = provider.getConfig().introspect(type);
result = BeanSerializerFactory.instance.findBeanSerializer(type, provider.getConfig(), beanDesc);
cache.put(type, result);
}
return result;
}
private Map<JavaType, JsonSerializer<Object>> cache = new HashMap<JavaType, JsonSerializer<Object>>();
private static class UnwrappingJsonGenerator extends JsonGeneratorDelegate
{
UnwrappingJsonGenerator(JsonGenerator d)
{
super(d);
}
#Override
public void writeEndObject() throws IOException, JsonGenerationException
{
if (depth-- >= yieldDepth) {
super.writeEndObject();
}
}
#Override
public void writeFieldName(SerializedString name) throws IOException, JsonGenerationException
{
if (depth >= yieldDepth) {
super.writeFieldName(name);
}
}
#Override
public void writeFieldName(String name) throws IOException, JsonGenerationException
{
if (depth >= yieldDepth) {
super.writeFieldName(name);
}
}
#Override
public void writeStartObject() throws IOException, JsonGenerationException
{
if (++depth >= yieldDepth) {
super.writeStartObject();
}
}
private int depth;
private final int yieldDepth = 2;
}
}
It will ignore outer objects on depth lower than specified (2 by default).
Then use it as follows:
public class UnwrappingSerializerTest
{
public static class BaseT1
{
public List<String> getTest()
{
return test;
}
public void setTest(List<String> test)
{
this.test = test;
}
private List<String> test;
}
#JsonSerialize(using = UnwrappingSerializer.class)
public static class T1 extends BaseT1
{
}
#JsonSerialize(using = UnwrappingSerializer.class)
public static class T2
{
public BaseT1 getT1()
{
return t1;
}
public void setT1(BaseT1 t1)
{
this.t1 = t1;
}
private BaseT1 t1;
}
#Test
public void test() throws IOException
{
ObjectMapper om = new ObjectMapper();
T1 t1 = new T1();
t1.setTest(Arrays.asList("foo", "bar"));
assertEquals("[\"foo\",\"bar\"]", om.writeValueAsString(t1));
BaseT1 baseT1 = new BaseT1();
baseT1.setTest(Arrays.asList("foo", "bar"));
T2 t2 = new T2();
t2.setT1(baseT1);
assertEquals("{\"test\":[\"foo\",\"bar\"]}", om.writeValueAsString(t2));
}
}
Notes:
It expects only single field wrapper and will generate invalid JSON on something like {{field1: {...}, field2: {...}}
If you use custom SerializerFactory you probably will need to pass it to the serializer.
It uses separate serializer cache so this also can be an issue.
Honestly, I wouldn't be too quick to try to fix this problem as having the wrapper does create a situation where your code is more extensible. Should you expand this in the future to return other objects, your clients consuming this web service most likely won't need to change the implementation.
However, if all clients expect an array that is unnamed, adding more properties in the future outside of that array may break the uniform interface.
With that said, everyone has their reasons for wanting to do something a certain way. What does the object look like that you are serializing? Are you serializing an object that contains an array, or are you serializing the actual array itself? If your POJO contains an array, then maybe the solution is to pull the array out of the POJO and serialize the array instead.
I stumbled upon this question while trying to solve the same problem, but was not using this with a #ResponseBody method, but was still encountering the "wrapper" in my serialized JSON. My solution was to add #JsonAnyGetter to the method/field, and then the wrapper would disappear from the JSON.
Apparently this is a known Jackson bug/workaround: http://jira.codehaus.org/browse/JACKSON-765.