I know there are similar questions around such as How to marshal/unmarshal a Map<Integer, List<Integer>>? and JAXB java.util.Map binding. Also I read Blaise Doughan's blog a lot especially this post: http://blog.bdoughan.com/2013/03/jaxb-and-javautilmap.html and tried to follow what he suggested as much as I can, however I still cannot unmarshal the json payload successfully and really appreciate your help.
The json payload to unmarshal looks like this:
{
"uri":"\\foo\\dosomthing",
"action":"POST",
"queryParameters":[
"$filter=aaa",
"$orderby=bbb"
],
"requestData":{
"data1":{
"key1":"value1",
"key2":"value2"
},
"ids":[
"1234",
"0294"
]
}
}
And I am having problem to unmarshal the "data" into the java.util.Map. The "data" field does not have specific schema so it can contains an array, key-value pairs or any other valid json data. I decided to use a Map to wrap it. Based on what I researched, I think I need XmlAdapter to convert the data properly.
Here are my code:
The Java Schema Class:
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class CustomerRequest
{
public CustomerRequest() {}
public CustomerRequest(String uri, String action, List<String>
queryParameters, Map<String, Object> reqeustData)
{
this.uri = uri;
this.action = action;
this.queryParameters = queryParameters;
this.requestData = reqeustData;
}
public String getUri()
{
return uri;
}
public String getAction()
{
return action;
}
public List<String> getQueryParameters()
{
return Collections.unmodifiableList(queryParameters);
}
public Map<String, Object> getRequestData()
{
return Collections.unmodifiableMap(requestData);
}
#XmlElement
private String uri;
#XmlElement
private String action;
#XmlElementWrapper
private List<String> queryParameters = new ArrayList<String>();
#XmlPath(".")
#XmlJavaTypeAdapter(StringObjectMapAdapter.class)
private Map<String, Object> requestData = new HashMap<String, Object>();
}
The XmlAdpater:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.eclipse.persistence.oxm.annotations.XmlVariableNode;
public class StringObjectMapAdapter extends
XmlAdapter<StringObjectMapAdapter.AdaptedMap, Map<String, Object>>
{
public static class AdaptedEntry
{
#XmlTransient
public String key;
#XmlValue
public Object value = new Object();
}
public static class AdaptedMap
{
#XmlVariableNode("key")
List<AdaptedEntry> entries = new ArrayList<AdaptedEntry>();
}
#Override
public AdaptedMap marshal(Map<String, Object> map) throws Exception
{
AdaptedMap adaptedMap = new AdaptedMap();
for (Entry<String, Object> entry : map.entrySet())
{
AdaptedEntry adaptedEntry = new AdaptedEntry();
adaptedEntry.key = entry.getKey();
adaptedEntry.value = entry.getValue();
adaptedMap.entries.add(adaptedEntry);
}
return adaptedMap;
}
#Override
public Map<String, Object> unmarshal(AdaptedMap adaptedMap) throws Exception
{
List<AdaptedEntry> adapatedEntries = adaptedMap.entries;
Map<String, Object> map = new HashMap<String, Object>(adapatedEntries.size());
for (AdaptedEntry adaptedEntry : adapatedEntries )
{
map.put(adaptedEntry.key, adaptedEntry.value);
}
return map;
}
}
and finally is my test app:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.testng.annotations.Test;
public class TestStringObjectMapAdapter {
#Test
public void testUnmarshalFromJson() throws Exception
{
JAXBContext jc = JAXBContext.newInstance(CustomerRequest.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
unmarshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false);
StreamSource json = new StreamSource("test-data.json");
CustomerRequest request= unmarshaller.unmarshal(json,
CustomerRequest.class).getValue();
assert(request.getUri().equals("\\foo\\dosomthing"));
assert(request.getAction().equals("POST"));
}
}
Then when test app runs, an java.lang.ClassCastException exception is generated:
FAILED: testUnmarshalFromJson
java.lang.ClassCastException: com.sun.org.apache.xerces.internal.dom.DocumentImpl cannot be cast to org.w3c.dom.Element
at org.eclipse.persistence.internal.oxm.XMLCompositeObjectMappingNodeValue.endSelfNodeValue(XMLCompositeObjectMappingNodeValue.java:468)
at org.eclipse.persistence.internal.oxm.record.UnmarshalRecordImpl.endDocument(UnmarshalRecordImpl.java:606)
at org.eclipse.persistence.internal.oxm.record.UnmarshalRecordImpl.endElement(UnmarshalRecordImpl.java:1084)
at org.eclipse.persistence.internal.oxm.record.json.JSONReader.parse(JSONReader.java:304)
at org.eclipse.persistence.internal.oxm.record.json.JSONReader.parseRoot(JSONReader.java:179)
at org.eclipse.persistence.internal.oxm.record.json.JSONReader.parse(JSONReader.java:125)
at org.eclipse.persistence.internal.oxm.record.json.JSONReader.parse(JSONReader.java:140)
at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:857)
at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:707)
at org.eclipse.persistence.oxm.XMLUnmarshaller.unmarshal(XMLUnmarshaller.java:655)
at org.eclipse.persistence.jaxb.JAXBUnmarshaller.unmarshal(JAXBUnmarshaller.java:301)
at com.absolute.asb.urp.services.domain.TestStringObjectMapAdapter.testUnmarshalFromJson(TestStringObjectMapAdapter.java:21)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
Maybe you should try creating the correct MoXY JAXBContext like:
private static synchronized JAXBContext createJAXBContext() throws JAXBException {
if(jc == null){
jc = org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(new Class[] {CustomerReqeust.class}, null);
}
return jc;
}
Or use another way like mentioned in http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
Btw "CustomerReqeust" is a little bit wrong spelled :-)
Related
I'm Java beginner. Just added JSON. Simple dependency and it's so easy to convert data to JSON format.
example :
public void Post_test3_positive(){
JSONObject user = new JSONObject();
user.put("name","John");
user.put("email","John#inbox.lv");
given()
.body(user.toJSONString())
.when().post("/api/user/createUserPost")
.then().statusCode(201)
Is there any easy way to convert request body (username and email ) to XML format?
Thanks
Hi try this to convert map to xml string :
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
map.put("name","chris");
map.put("island","faranga");
XStream magicApi = new XStream();
magicApi.registerConverter(new MapEntryConverter());
magicApi.alias("root", Map.class);
String xml = magicApi.toXML(map);
System.out.println("Result of tweaked XStream toXml()");
System.out.println(xml);
Map<String, String> extractedMap = (Map<String, String>) magicApi.fromXML(xml);
assert extractedMap.get("name").equals("chris");
assert extractedMap.get("island").equals("faranga");
}
public static class MapEntryConverter implements Converter {
public boolean canConvert(Class clazz) {
return AbstractMap.class.isAssignableFrom(clazz);
}
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
AbstractMap map = (AbstractMap) value;
for (Object obj : map.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
writer.startNode(entry.getKey().toString());
Object val = entry.getValue();
if ( null != val ) {
writer.setValue(val.toString());
}
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Map<String, String> map = new HashMap<String, String>();
while(reader.hasMoreChildren()) {
reader.moveDown();
String key = reader.getNodeName(); // nodeName aka element's name
String value = reader.getValue();
map.put(key, value);
reader.moveUp();
}
return map;
}
}
}
I use java POJO and convert it to json/xml by using Jackson library.
#Data
#AllArgsConstructor
#XmlRootElement
#NoArgsConstructor
static class Example {
private String name;
private String email;
}
#Test
void name() {
Example payload = new Example("John", "John#inbox.lv");
given().log().body()
.contentType(ContentType.JSON)
.body(payload)
.when().post("https://postman-echo.com/post");
given().log().body()
.contentType(ContentType.XML)
.body(payload)
.when().post("https://postman-echo.com/post");
}
result:
Body:
{
"name": "John",
"email": "John#inbox.lv"
}
Body:
<example>
<email>John#inbox.lv</email>
<name>John</name>
</example>
pom.xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
Here are my bean classes:
package request;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
#JacksonXmlRootElement
public class Employee {
private List<String> roles= new ArrayList<String>();
private String name;
public Employee(){}
#JacksonXmlProperty
public String getName ()
{
return name;
}
public void setName (String name)
{
this.name = name;
}
#JacksonXmlElementWrapper(useWrapping=false)
#JacksonXmlProperty
public List<String> getRoles ()
{
return roleCodes;
}
public void setRoles (String role)
{
this.roles.add(role);
}
}
and,
package request;
import java.util.ArrayList;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
public class Employees
{
#JacksonXmlElementWrapper(localName="employees")
#JacksonXmlProperty(localName="employee")
private ArrayList<Employee> emps;
//Employee Employee ;
public Employees(){}
#JacksonXmlProperty(localName="employee")
public ArrayList<Employee> getEmployees ()
{
return emps;
}
public void setEmployees(Employee emp){
this.emps.add(emp);
}
#Override
public String toString()
{
if(emps.isEmpty()!=true)
for (Employee e:emps)
return "this is [employee = "+e ;
return "none there";
}
public ArrayList<Employee> addingEmployee(Employee e){
this.emps.add(e);
return emps;
}
}
And here is the code to parse the xml into POJO:
package testPkg4;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import request.Bean;
import request.Employee;
import request.Employees;
public class Test4 {
public static void main(String[] args) {
XmlMapper xmlMapper = new XmlMapper();
//Bean value = new Bean();
Employees emps=new Employees();
try {
emps = xmlMapper.readValue(new File("D:\\workspace\\test\\src\\test\\resources\\employee.xml"),
Employees.class);
} catch (IOException ex) {
// TODO Auto-generated catch block
ex.printStackTrace();
}
System.out.println(emps.getEmployees().get(0).getFirstName());
//System.out.println(e.getFirstName());
//System.out.println(emps.getEmployees().get(0).getThirdElement());
}
}
Now here is the error I am getting :
com.fasterxml.jackson.databind.JsonMappingException: N/A at [Source:
D:\workspace\test\src\test\resources\employee.xml; line: 5, column:
12] (through reference chain: request.Employees["employee"]) at
com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:277)
at
com.fasterxml.jackson.databind.deser.SettableBeanProperty._throwAsIOE(SettableBeanProperty.java:551)
at
com.fasterxml.jackson.databind.deser.SettableBeanProperty._throwAsIOE(SettableBeanProperty.java:532)
at
com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:108)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:276)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:140)
at
com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3814)
at
com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2756)
at testPkg4.Test4.main(Test4.java:23) Caused by:
java.lang.NullPointerException at
request.Employees.setEmployees(Employees.java:31) at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at
sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at
sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at
java.lang.reflect.Method.invoke(Unknown Source) at
com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:106)
... 5 more Exception in thread "main" java.lang.NullPointerException
at testPkg4.Test4.main(Test4.java:29)
while i am parsing thsi xml file:
<employees>
<employee>
<name>ASHISH</name>
<roles>MD</roles>
</employee>
<employee>
<name>BAHADUR</name>
<roles>CO</roles>
<roles>TM</roles>
</employee>
</employees>
Can anyone help me figure out what's the issue!
Found an useful tutorial to create custom untyped xml deserializer which helped me overcome it .
and in case of serialization create java classes from schema using jaxb2-maven-pluging as a build plug in .
org.codehaus.mojo
jaxb2-maven-plugin
1.5
xjc
-extension -npa -b ${project.basedir}/src/main/xsd/global.xjb
** if you are using jackson library then either replace the annotations with the jackson alternatives for jaxb annotaions . or register the jaxbannotation module into your serializer .
Here is the link for the gist that helped me .
My JSON looks like this:
{"typeName":"test","field":{"name":"42"}}
I have two deserializers. The first one (JsonDeserializer<EntityImpl>) will examine the JSON and extract a type information which is provided by the typeName property.
The second deserializer (JsonDeserializer<TestField>) is used to deserialize the field property. This deserializer needs to know the previously extracted typeName value in order to work correctly.
How can i pass-along the type information from one deserializer to other deserializers? I tried to use DeserializationContext but i don't know how to pass along the Context from deserializer A to B.
My current code looks like this:
EntityImpl.java:
package de.jotschi.test;
public class EntityImpl implements Entity {
private String typeName;
private TestField field;
public String getTypeName() {
return typeName;
}
public void setTypeName(String typeName) {
this.typeName = typeName;
}
public TestField getField() {
return field;
}
public void setField(TestField field) {
this.field = field;
}
}
TestField.java:
package de.jotschi.test;
public class TestField {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Test:
package de.jotschi.test;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import de.jotschi.test.EntityImpl;
import de.jotschi.test.TestField;
public class TestMapper2 {
private InjectableValues getInjectableValue() {
InjectableValues values = new InjectableValues() {
#Override
public Object findInjectableValue(Object valueId, DeserializationContext ctxt, BeanProperty forProperty, Object beanInstance) {
if ("data".equals(valueId.toString())) {
return new HashMap<String, String>();
}
return null;
}
};
return values;
}
#Test
public void testMapper() throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule idAsRefModule = new SimpleModule("ID-to-ref", new Version(1, 0, 0, null));
idAsRefModule.addDeserializer(EntityImpl.class, new JsonDeserializer<EntityImpl>() {
#Override
public EntityImpl deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null);
System.out.println("Value: " + dataMap.get("test"));
ObjectCodec codec = jp.getCodec();
JsonNode node = codec.readTree(jp);
String type = node.get("typeName").asText();
dataMap.put("typeName", type);
// How to pass on type information to TestField deserializer? The context is not reused for the next deserializer.
// I assume that readValueAs fails since the codec.readTree method has already been invoked.
//return jp.readValueAs(EntityImpl.class);
// Alternatively the treeToValue method can be invoked in combination with the node. Unfortunately all information about the DeserializationContext is lost. I assume new context will be created.
// How to reuse the old context?
return codec.treeToValue(node, EntityImpl.class);
}
});
idAsRefModule.addDeserializer(TestField.class, new JsonDeserializer<TestField>() {
#Override
public TestField deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// Access type from context
Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null);
System.out.println(dataMap.get("typeName"));
ObjectCodec codec = p.getCodec();
JsonNode node = codec.readTree(p);
return codec.treeToValue(node, TestField.class);
}
});
mapper.registerModule(idAsRefModule);
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// Setup the pojo
EntityImpl impl = new EntityImpl();
impl.setTypeName("test");
TestField testField = new TestField();
testField.setName("42");
impl.setField(testField);
// POJO -> JSON
String json = mapper.writeValueAsString(impl);
System.out.println(json);
// JSON -> POJO
Entity obj = mapper.reader(getInjectableValue()).forType(EntityImpl.class).readValue(json);
System.out.println(obj.getClass().getName());
}
}
My current solution is call the following mapper this way:
return mapper.setInjectableValues(getInjectableValue(dataMap)).treeToValue(obj, EntityImpl.class);
This way the previously loaded context data map is put into a new context that is used for the following parsing process.
Full example:
package de.jotschi.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class TestMapper2 {
private InjectableValues getInjectableValue(final Map<String, String> dataMap) {
InjectableValues values = new InjectableValues() {
#Override
public Object findInjectableValue(Object valueId, DeserializationContext ctxt, BeanProperty forProperty, Object beanInstance) {
if ("data".equals(valueId.toString())) {
return dataMap;
}
return null;
}
};
return values;
}
#Test
public void testMapper() throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule idAsRefModule = new SimpleModule("ID-to-ref", new Version(1, 0, 0, null));
idAsRefModule.addDeserializer(Entity.class, new JsonDeserializer<Entity>() {
#Override
public Entity deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null);
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode obj = (ObjectNode) mapper.readTree(jp);
String type = obj.get("typeName").asText();
dataMap.put("typeName", type);
return mapper.setInjectableValues(getInjectableValue(dataMap)).treeToValue(obj, EntityImpl.class);
}
});
idAsRefModule.addDeserializer(TestField.class, new JsonDeserializer<TestField>() {
#Override
public TestField deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// Access type from context
Map<String, String> dataMap = (Map) ctxt.findInjectableValue("data", null, null);
System.out.println("Type name: " + dataMap.get("typeName"));
ObjectMapper mapper = (ObjectMapper) p.getCodec();
ObjectNode obj = (ObjectNode) mapper.readTree(p);
// Custom deserialisation
TestField field = new TestField();
field.setName(obj.get("name").asText());
// Delegate further deserialisation to other mapper
field.setSubField(mapper.setInjectableValues(getInjectableValue(dataMap)).treeToValue(obj.get("subField"), SubField.class));
return field;
}
});
mapper.registerModule(idAsRefModule);
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// Setup the pojo
EntityImpl impl = new EntityImpl();
impl.setTypeName("test");
TestField testField = new TestField();
testField.setName("42");
SubField subField = new SubField();
subField.setName("sub");
testField.setSubField(subField);
impl.setField(testField);
// POJO -> JSON
String json = mapper.writeValueAsString(impl);
System.out.println(json);
// JSON -> POJO
Entity obj = mapper.reader(getInjectableValue(new HashMap<String, String>())).forType(Entity.class).readValue(json);
assertNotNull("The enity must not be null", obj);
assertNotNull(((EntityImpl) obj).getField());
assertEquals("42", ((EntityImpl) obj).getField().getName());
assertNotNull(((EntityImpl) obj).getField().getSubField());
assertEquals("sub", ((EntityImpl) obj).getField().getSubField().getName());
System.out.println(obj.getClass().getName());
}
}
I wrote the following JsonSerializer to let Jackson serialize an array of integers into JSON:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class TalkIdsSerializer extends JsonSerializer<TalkIds> {
/**
* Serializes a TalkIds object into the following JSON string:
* Example: { "talk_ids" : [ 5931, 5930 ] }
*/
#Override
public void serialize(TalkIds talkIds, JsonGenerator jsonGenerator,
SerializerProvider provider)
throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeArrayFieldStart(TalkIds.API_DICTIONARY_KEY);
for (Integer talkId : talkIds.getTalkIds()) {
jsonGenerator.writeNumber(talkId);
}
jsonGenerator.writeEndArray();
jsonGenerator.writeEndObject();
}
}
The class is used here:
#JsonSerialize(using = TalkIdsSerializer.class)
public class TalkIds { /* ... */ }
I want test the behavior of the serializer and came up with the following:
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class TalkIdsSerializerTest {
protected final ArrayList<Integer> TALK_IDS =
new ArrayList<>(Arrays.asList(5931, 5930));
protected TalkIdsSerializer talkIdsSerializer;
#Before
public void setup() throws IOException {
talkIdsSerializer = new TalkIdsSerializer();
}
#Test
public void testSerialize() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonGenerator jsonGenerator =
new JsonFactory().createGenerator(stringWriter);
TalkIds talkIds = new TalkIds();
talkIds.add(TALK_IDS);
talkIdsSerializer.serialize(talkIds, jsonGenerator, null);
String string = stringWriter.toString(); // string is ""
assertNotNull(string);
assertTrue(string.length() > 0);
stringWriter.close();
}
}
However, nothing is written to the StringWriter. What am I doing wrong?
You need to flush() the generator
Method called to flush any buffered content to the underlying target (output stream, writer), and to flush the target itself as well.
http://fasterxml.github.io/jackson-core/javadoc/2.1.0/com/fasterxml/jackson/core/JsonGenerator.html#flush()
I had a similar requirement, to test a custom serializer. I used objectMapper to get the string directly(since you have already annotated TalkIds with JsonSerialize). You can get the json string from the object as follows
String json = new ObjectMapper().writeValueAsString(talkIds)
For me flush() changed nothing, so I changed the way to test it, in accordance with http://www.baeldung.com/jackson-custom-serialization.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.StringWriter;
//...
#Test
public void serialize_custom() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(MyCustomSerializer.class, myCustomSerializer);
objectMapper.registerModule(module);
StringWriter stringWriter = new StringWriter();
TalkIds talkIds = new TalkIds();
talkIds.add(TALK_IDS);
objectMapper.writeValue(stringWriter,wi);
assertTrue(stringWriter.toString().length() > 3);
}
I know how to pretty print the JSONby default, but I want to give the choice up to the user. Thats why I want to make it configureable via QueryParam.
This should pretty print json (if not given default is false):
...test123/res123?pretty=T
...test123/res123?pretty=True
...test123/res123?pretty=t
...test123/res123?pretty=true
Does someone have a good idea to do this without copying the same code to thousands resources? Should I do this with a messagebody writer? Or outgoing filter?
The solution (Thanks to Alexey Gavrilov for the hint):
import java.io.IOException;
import javax.ws.rs.core.MultivaluedMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.jaxrs.cfg.EndpointConfigBase;
import com.fasterxml.jackson.jaxrs.cfg.ObjectWriterModifier;
public class IndentingModifier extends ObjectWriterModifier {
private static final Logger LOG = LoggerFactory.getLogger(IndentingModifier.class);
public static boolean doIndent = false;
public final Boolean _indent;
public IndentingModifier() {
this(null);
}
public IndentingModifier(
Boolean indent) {
_indent = indent;
}
#Override
public ObjectWriter modify(
EndpointConfigBase<?> endpoint,
MultivaluedMap<String, Object> responseHeaders,
Object valueToWrite,
ObjectWriter w,
JsonGenerator g) throws IOException {
if (_indent != null) {
if (_indent.booleanValue()) {
LOG.debug("Using default pretty printer, because ident is null.");
g.useDefaultPrettyPrinter();
}
} else {
if (doIndent) {
LOG.debug("Using default pretty printer, because ident is true.");
g.useDefaultPrettyPrinter();
}
}
return w;
}
}
And the Container Filter:
import java.io.IOException;
import java.util.List;
import java.util.Map.Entry;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.jaxrs.cfg.ObjectWriterInjector;
#Provider
public class JsonPrettryPrintQueryParamContainerResponseFilter implements javax.ws.rs.container.ContainerResponseFilter {
private static final Logger LOG = LoggerFactory.getLogger(JsonPrettryPrintQueryParamContainerResponseFilter.class);
private static final String QUERY_PARAM_PRETTY = "pretty";
private static final String QUERY_PARAM_T = "t";
private static final String QUERY_PARAM_TRUE = "true";
private static final String QUERY_PARAM_F = "f";
private static final String QUERY_PARAM_False = "false";
#Override
public void filter(
ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
MultivaluedMap<String, String> queryParams = requestContext.getUriInfo().getQueryParameters();
for (Entry<String, List<String>> queryEntry : queryParams.entrySet()) {
if (queryEntry.getKey().equalsIgnoreCase(QUERY_PARAM_PRETTY)) {
String valueOfLastQueryParamIndex = queryEntry.getValue().get(queryEntry.getValue().size() - 1);
LOG.debug(String.format("Found queryPram '%s' with value '%s'.", queryEntry.getKey(),
valueOfLastQueryParamIndex));
switch (valueOfLastQueryParamIndex.toLowerCase()) {
case QUERY_PARAM_T:
ObjectWriterInjector.set(new IndentingModifier(true));
break;
case QUERY_PARAM_TRUE:
ObjectWriterInjector.set(new IndentingModifier(true));
break;
case QUERY_PARAM_F:
ObjectWriterInjector.set(new IndentingModifier(false));
break;
case QUERY_PARAM_False:
ObjectWriterInjector.set(new IndentingModifier(false));
break;
default:
break;
}
break;
}
}
}
}
In extended application class run():
environment.jersey().register(JsonPrettryPrintQueryParamContainerResponseFilter.class);
You can use ObjectWriterInjector and ObjectWriterModifier to customize the object writer in the resource method depending on the query parameter.
Take a look at this code sample from the Jackson JAX-RS provider repository.