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.
Related
Consider the following object
#Value
public class Example {
#JsonProperty("StrValue")
String strValue;
#JsonProperty("Internal")
Internal internal;
#Value
public static class Internal {
#JsonProperty("InternalValue")
String internalValue;
}
}
I could get a serialization of this object like
"Example" : {
"StrValue": "v1",
"Internal": {
"InternalValue": "v2"
}
}
However, I need two different versions of this object, one would have the string First appended to the name of all properties and the other would have Second:
"ExampleFirst" : {
"StrValueFirst": "v1",
"InternalFirst": {
"InternalValueFirst": "v2"
}
}
(And similarly for Second)
Note that this should work for nested properties and for any object of any class. Is there an easy way to do this? I feel like this might be the work of BeanSerializerModifier or some other custom serialization mechanisms, however, the custom serialization APIs are pretty low-level and the documentation is scarce.
Answering my own question since I (kind of) solved it:
First I would make a subclass of BeanPropertyWriter such as
class AppendingPropertyWriter extends BeanPropertyWriter {
private final BeanPropertyWriter writer;
public AppendingPropertyWriter(BeanPropertyWriter writer) {
super(writer);
this.writer = writer;
}
#Override
public void serializeAsField(Object bean,
JsonGenerator gen,
SerializerProvider prov) throws Exception {
Object value = writer.get(bean);
gen.writeObjectField(writer.getName() + "First", value);
}
}
Then add it to the BeanSerializerModifier like:
public class AppendingSerializerModifier extends BeanSerializerModifier {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
for (int i = 0; i < beanProperties.size(); i++) {
BeanPropertyWriter writer = beanProperties.get(i);
beanProperties.set(i, new AppendingPropertyWriter(writer));
}
return beanProperties;
}
}
Then you'd have to put it into your ObjectMapper like:
var serializerModifier = new AppendingSerializerModifier();
var sf = BeanSerializerFactory.instance.withSerializerModifier(serializerModifier);
ObjectMapper mapper = new XmlMapper();
mapper.setSerializerFactory(sf);
Then this mapper achieves the intended effect. However this seems to bypass some other modules I added like JavaTimeModule and throw an exception with them. So if anyone has a solution to that I'd be grateful.
EDIT: Fixed it! Turns out I needed to serialize the object as
#Override
public void serializeAsField(Object bean,
JsonGenerator gen,
SerializerProvider prov) throws Exception {
Object value = writer.get(bean);
prov.defaultSerializeField(writer.getName() + "First", value, gen);
}
And then be sure to register the JavaTime module after setting the factory
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");
}
}
I have the provider interface
interface IProvider<T> {
T locate();
}
and a class containing a field of type IProvider (can be another type for other fields).
class MyObject {
MyLocator<String> field;
}
I need to serialize instances of MyObject to JSON using Jackson 1.7. The output must be the same as if MyObject.field had been a String (i.e. no reference to ILocator).
I can't figure out how to build the custom serializer required to achieve this. Here is the structure I am trying to use for this task:
class MyLocatorSerializer extends SerializerBase<MyLocator<?>> {
public MyLocatorSerializer() {
super(MyLocator.class, false);
}
#Override
public void serialize(MyLocator<?> a_value, JsonGenerator a_jgen,
SerializerProvider a_provider) throws IOException, JsonGenerationException {
// Insert code here to serialize a_value.locate(), whatever its type
}
#Override
public JsonNode getSchema(SerializerProvider a_provider, Type a_typeHint)
throws JsonMappingException {
// What should I return here? I can't find documentation regarding the different schema types...
}
}
The custom serializer would be registered using
SimpleModule module = new SimpleModule("MyModule", new Version(1, 0, 0, null));
module.addSerializer(new MyLocatorSerializer());
objectMapper.registerModule(module);
Another answer using mix-in annotations following the comment from Staxman.
static class JacksonCustomModule extends SimpleModule {
public JacksonCustomModule() {
super("JacksonCustomModule", new Version(1, 0, 0, null));
}
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(IProvider.class, IProviderMixIn.class);
super.setupModule(context);
}
interface IProviderMixIn<T> {
#JsonValue
T locate();
}
}
Activate the module with:
objectMapper.registerModule(new JacksonCustomModule());
Apologies if I misunderstand the question, but would this be as simple as just using #JsonValue on 'Locate' method, instead of writing a custom serializer?
What #JsonValue does is take value of a property as is, and use it instead of creating a JSON Object: often this is used for serializing a POJO as a simple String or number, like so:
public class StringWrapper {
#JsonValue public String value;
}
so that for class like:
public class POJO {
public StringWrapper wrapped;
}
we would get serialization like:
{
"wrapper" : "string value of 'value'"
}
instead of what would otherwise be seen:
{
"wrapper" : {
"value" : "... string value ... "
}
}
Annotation can be used for any types of values obviously.
Following StaxMan's answer, I inspected the workings of #JsonValue and got the following serializer:
// Based on JsonValueSerializer
private static class ProviderSerializer extends SerializerBase<IProvider<?>> {
public ProviderSerializer() {
super(IProvider.class, false);
}
#Override
public void serialize(IProvider<?> value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException {
Object object = value.locate();
// and if we got null, can also just write it directly
if (object == null) {
provider.defaultSerializeNull(jgen);
return;
}
Class<?> c = object.getClass();
JsonSerializer<Object> ser = provider.findTypedValueSerializer(c, true, null);
// note: now we have bundled type serializer, so should NOT call with typed version
ser.serialize(object, jgen, provider);
}
#Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint)
throws JsonMappingException {
// is this right??
return JsonSchema.getDefaultSchemaNode();
}
}
After some tests, this does what I need. However, I don't fully really understand the purpose of the getSchema method, so maybe I'm doing something wrong...
I've got a bean defined as such :
public static class TestBean {
private String a;
private Long b;
public TestBean(String a, Long b) {
this.a = a;
this.b = b;
}
public String getA() {
return a;
}
public Long getB() {
return b;
}
}
It models some business and I do not get to instantiate it (using JPA). Some of my API let the user retrieve a view of this bean serialize as JSON using Jackson (through JAX-RS) and I would like to add a list of related links to this view.
The normal Jackson JSON serialization would be (for a = "aa" and b = 2L) :
{"a":"aa","b":2}
And I would like to have the links appear as
{"a":"aa","b":2,
"links":[{"rel":"rel","href":"href://"},{"rel":"rel2","href":"href://2"}]}
Possible work-around
I would rather not add a getLinks() method to my bean, it's specific to this view.
Simply using a composite object would yield a serialization like :
{"data":{"a":"aa","b":2},"links":[{"rel":"rel","href":"href://"}]}
Which I could live with but is not what I was looking for ultimately.
Current solution
I would like to avoid manipulating the JSON string or having to reload it into a Map to insert my extra values. For now the solution I've come up with seem awfully convoluted:
Current scary solution :
//a composite view object
public abstract class AddedLinksView<K> {
private final K resource;
private final Link[] links;
public AddedLinksView(K resource) {
this.resource = resource;
links = buildLinks(resource);
}
public abstract Link[] buildLinks(K resource);
public K getResource() {
return resource;
}
public Link[] getLinks() {
return links;
}
}
//a specific bean serializer
private static class RawBeanSerializer extends BeanSerializer {
public RawBeanSerializer(BeanSerializerBase ser) {
super(ser);
}
//this is like the standard serialize but without the start and end tags
public void rawSerialize(Object bean, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonGenerationException {
if (_propertyFilterId != null) {
serializeFieldsFiltered(bean, jgen, provider);
} else {
serializeFields(bean, jgen, provider);
}
}
}
#Test
public void usingModule() throws Exception {
// basic module metadata just includes name and version (both for troubleshooting; but name needs to be unique)
SimpleModule module = new SimpleModule("EnhancedDatesModule", new Version(0, 1, 0, "alpha"));
//adding a serializer for the composite view
module.addSerializer(new JsonSerializer<AddedLinksView>() {
#Override
public Class<AddedLinksView> handledType() {
return AddedLinksView.class;
}
#Override
public void serialize(AddedLinksView value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
//looking up the bean serializer that will be used for my resource
JsonSerializer<Object> ser = provider.findTypedValueSerializer(value.getResource().getClass(), true,
null);
if (ser instanceof BeanSerializerBase) {
//cloning it in a sub class that makes it possible to 'inline' the serialization
RawBeanSerializer openSer = new RawBeanSerializer((BeanSerializerBase) ser);
openSer.rawSerialize(value.getResource(), jgen, provider);
}
//adding my links
jgen.writeArrayFieldStart("links");
for (Link link : value.getLinks()) {
jgen.writeObject(link);
}
jgen.writeEndArray();
jgen.writeEndObject();
}
});
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
AddedLinksView<TestBean> view = new AddedLinksView<TestBean>(new TestBean("aa", 2L)) {
#Override
public Link[] buildLinks(TestBean resource) {
return new Link[] { new Link("rel", "href://"), new Link("rel2", "href://2") };
}
};
System.out.println("useModule json output: " + mapper.writeValueAsString(view));
}
Did I miss something obvious in Jackson to achieve this? Or am I completely off the mark in my requirements already?
There is no real way to externally inject things into POJOs to serialize: but you might be interested in checking out #JsonAnyGetter, which at least allows just adding contents of a java.util.Map as extra properties for a POJO.
Would this answer your question: http://wiki.fasterxml.com/JacksonFeatureUpdateValue
I am not sure you can avoid mapping. You may use Dozer to help.
This should help you: Tools for merging java beans
I have a class hierarchy similar to this one:
public static class BaseConfiguration {
}
public abstract class Base {
private BaseConfiguration configuration;
public String id;
public BaseConfiguration getConfiguration() { ... }
public void setConfiguration(BaseConfiguration configuration) { ... }
}
public class A extends Base {
public static class CustomConfigurationA extends BaseConfiguration {
String filename;
String encoding;
}
CustomConfigurationA getConfiguration() { ... }
}
class B extends Base {
public static class CustomConfigurationB extends BaseConfiguration {
/* ... */
}
CustomConfigurationB getConfiguration() { ... }
}
And json input like this one (which I cannot change myself)
{
"id":"File1",
"configuration":{
"filename":"...",
"encoding":"UTF-8"
}
}
I am parsing the JSON in Java with Jackson like this
ObjectMapper mapper = new ObjectMapper();
value = mapper.readValue(in, nodeType);
I want to deserialize classes A, B and others from JSON using JAVA/Jackson. There are no type information embedded in JSON (and can't be). I can't use annotations on the classes (I don't own them) and I (believe) I can't use mixins since there are potentially arbitrary numbers of classes like A & B (and mixins are not dynamic). Good thing is that the deserializing code knows which is the correct custom class to use for deserializing (basically there is a known mapping from class to configuration class), but I do not know how make Jackson recognize this information when deserializing the JSON.
In short: I want to be able to resolve the deserialization type of the configuration object depending on the surrounding class type by setting whatever is necessary on ObjectMapper. How can this be achieved?
Apparently the answer was to implement something similar to the sixth solution posted at http://programmerbruce.blogspot.com/2011/05/deserialize-json-with-jackson-into.html, which uses unique JSON element names to identify the target type to deserialize to.
Good answer provided by Programmer Bruce!
I have a case of polymorphism in which I want to keep the domain objects as POJOs and not use dependencies on Jackson annotations.
Therefore I preffer to use a custom deserializer and a Factory for decising the type or intantiating the concrete classes.
Here is my code ...
(be aware that I have an Annotation Hierarchy which are in fact "User Tags" and not Java Annotations )
Here is the deserialization Method
public class AnnotationDeserializer extends StdDeserializer<Annotation> {
AnnotationDeserializer() {
super(Annotation.class);
}
#Override
public Annotation deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode root = (ObjectNode) mapper.readTree(jp);
Class<? extends Annotation> realClass = null;
Iterator<Entry<String, JsonNode>> elementsIterator = root.getFields();
while (elementsIterator.hasNext()) {
Entry<String, JsonNode> element = elementsIterator.next();
if ("type".equals(element.getKey())) {
realClass = AnnotationObjectFactory.getInstance()
.getAnnotationClass(element.getKey());
break;
}
}
if (realClass == null)
return null;
return mapper.readValue(root, realClass);
}
}
I had to do something similar, and ended up creating a generic polymorphic list serializer and deserialzer. Here is the deserialize that I think will work for you:
public class PolymorphicListDeserializer extends JsonDeserializer<List<?>> implements ContextualDeserializer {
private HashMap<String, Class> _typeMap = null;
private Class _elementType;
private static <T> List<T> getNewList(Class<T> clazz) {
return new ArrayList<T>();
}
#Override
public List<?> deserialize(final JsonParser jp, DeserializationContext ctxt) throws IOException {
final List list = getNewList(_elementType);
JsonToken nextToken = jp.getCurrentToken();
if (nextToken == JsonToken.START_OBJECT) {
if ( _typeMap.containsKey( currentFieldName )) {
list.add( _elementType.cast( ctxt.readValue( jp, _typeMap.get( currentFieldName ) ) ) );
}
nextToken = jp.nextToken();
} else if (currentFieldName != null && isEndToken(nextToken) && wrapperCount == 0) {
break;
} else {
nextToken = jp.nextToken();
}
}
return list;
}
public JsonDeserializer<List<?>> createContextual( DeserializationContext ctxt, BeanProperty property ) throws JsonMappingException {
//In Jackson 2.6.3, this method is called once per instance and the exception is never thrown
if ( _typeMap == null )
_typeMap = new HashMap<String, Class>();
else
throw new RuntimeException("Unsupported version of Jackson. Code assumes context is created once and only once.");
_elementType = property.getType().getContentType().getRawClass();
//For now, requiring XmlElements annotation to work. May add support for JsonElements (if needed) later.
for (XmlElement e : property.getAnnotation(XmlElements.class).value()) {
_typeMap.put(e.name(), e.type());
}
return this;
}
private static boolean isStartToken(JsonToken t) {
boolean result = false;
if (t == JsonToken.START_OBJECT) {
result = true;
} else if (t == JsonToken.START_ARRAY) {
result = true;
}
return result;
}
Above answers depicts a solution however lack what actually used annotations mean. If you are curious about what actually these annotation do, idea behind them & why they are required please go through the below link. Its explained very nicely in it. https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization