in my program I need to store object in XML. But I dont want all properties to be serialized to xml. How should I do that ?
public class Car implements ICar{
//all variables has their own setters and getters
private String manufacturer;
private String plate;
private DateTime dateOfManufacture;
private int mileage;
private int ownerId;
private Owner owner; // will not be serialized to xml
.....
}
//code for serialize to xml
public static String serialize(Object obj)
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
XMLEncoder encoder = new XMLEncoder(baos);
encoder.writeObject(obj);
encoder.close();
return baos.toString();
}
Check out this link. Here's an updated example.
BeanInfo info = Introspector.getBeanInfo(Car.class);
PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors();
for (int i = 0; i < propertyDescriptors.length; ++i) {
PropertyDescriptor pd = propertyDescriptors[i];
if (pd.getName().equals("dateOfManufacture")) {
pd.setValue("transient", Boolean.TRUE);
}
}
I choose to use JAXB serialization with annotations. It was the best and easiest option. Thank all of you for your help.
public static String serialize(Object obj) throws JAXBException
{
StringWriter writer = new StringWriter();
JAXBContext context = JAXBContext.newInstance(obj.getClass());
Marshaller m = context.createMarshaller();
m.marshal(obj, writer);
return writer.toString();
}
public static Object deserialize(String xml, Object obj) throws JAXBException
{
StringBuffer xmlStr = new StringBuffer(xml);
JAXBContext context = JAXBContext.newInstance(obj.getClass());
Unmarshaller um = context.createUnmarshaller();
return um.unmarshal(new StreamSource(new StringReader(xmlStr.toString())));
}
Related
Is there a simple way to serialize an object using Jackson to base64 encoded JSON? (object -> JSON -> base64)
I tried using a custom StdSerializer, but this (of course) results in a endless loop:
class MySerializer extends StdSerializer<Foo> {
public void serialize(Foo value, JsonGenerator gen, SerializerProvider provider) {
StringWriter stringWriter = new StringWriter();
JsonGenerator newGen = gen.getCodec().getFactory().createGenerator(stringWriter);
gen.getCodec().getFactory().getCodec().writeValue(newGen, value);
String json = stringWriter.toString();
String base64 = new String(Base64.getEncoder().encode(json.getBytes()));
gen.writeString(base64);
}
}
A workaround is to copy all fields to another class and use that class for the intermediate representation:
class TmpFoo {
public String field1;
public int field2;
// ...
}
class MySerializer extends StdSerializer<Foo> {
public void serialize(Foo value, JsonGenerator gen, SerializerProvider provider) {
TmpFoo tmp = new TmpFoo();
tmp.field1 = value.field1;
tmp.field2 = value.field2;
// etc.
StringWriter stringWriter = new StringWriter();
JsonGenerator newGen = gen.getCodec().getFactory().createGenerator(stringWriter);
gen.getCodec().getFactory().getCodec().writeValue(newGen, tmp); // here "tmp" instead of "value"
String json = stringWriter.toString();
String base64 = new String(Base64.getEncoder().encode(json.getBytes()));
gen.writeString(base64);
}
}
Creating a new ObjectMapper is not desired, because I need all registered modules and serializers of the default ObjectMapper.
I was hoping for some easier way of achieving this.
EDIT: Example
Step 1: Java Object
class Foo {
String field1 = "foo";
int field2 = 42;
}
Step 2: JSON
{"field1":"foo","field2":42}
Step 3: Base64
eyJmaWVsZDEiOiJmb28iLCJmaWVsZDIiOjQyfQ==
According to this site, there is a workaround to avoid this recursion problem:
When we define a custom serializer, Jackson internally overrides the
original BeanSerializer instance [...] our SerializerProvider finds
the customized serializer every time, instead of the default one, and
this causes an infinite loop.
A possible workaround is using BeanSerializerModifier to store the
default serializer for the type Folder before Jackson internally
overrides it.
If I understood the workaround correctly, your Serializer should look like this:
class FooSerializer extends StdSerializer<Foo> {
private final JsonSerializer<Object> defaultSerializer;
public FooSerializer(JsonSerializer<Object> defaultSerializer) {
super(Foo.class);
this.defaultSerializer = defaultSerializer;
}
#Override
public void serialize(Foo value, JsonGenerator gen, SerializerProvider provider) throws IOException {
StringWriter stringWriter = new StringWriter();
JsonGenerator tempGen = provider.getGenerator().getCodec().getFactory().createGenerator(stringWriter);
defaultSerializer.serialize(value, tempGen, provider);
tempGen.flush();
String json = stringWriter.toString();
String base64 = new String(Base64.getEncoder().encode(json.getBytes()));
gen.writeString(base64);
}
}
In addition to the serializer, a modifier is needed:
public class FooBeanSerializerModifier extends BeanSerializerModifier {
#Override
public JsonSerializer<?> modifySerializer(
SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
if (beanDesc.getBeanClass().equals(Foo.class)) {
return new FooSerializer((JsonSerializer<Object>) serializer);
}
return serializer;
}
}
Example module:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setSerializerModifier(new FooBeanSerializerModifier());
mapper.registerModule(module);
EDIT:
I've added flush() to flush the JsonGenerator tempGen.
Also, I've created a minimal test enviroment with JUnit, which verifies your Example with Foo: The github repo can be found here.
EDIT: Alternative 2
Another (simple) option is using a wrapper class with generics:
public class Base64Wrapper<T> {
private final T wrapped;
private Base64Wrapper(T wrapped) {
this.wrapped = wrapped;
}
public T getWrapped() {
return this.wrapped;
}
public static <T> Base64Wrapper<T> of(T wrapped) {
return new Base64Wrapper<>(wrapped);
}
}
public class Base64WrapperSerializer extends StdSerializer<Base64Wrapper> {
public Base64WrapperSerializer() {
super(Base64Wrapper.class);
}
#Override
public void serialize(Base64Wrapper value, JsonGenerator gen, SerializerProvider provider) throws IOException {
StringWriter stringWriter = new StringWriter();
JsonGenerator tempGen = provider.getGenerator().getCodec().getFactory().createGenerator(stringWriter);
provider.defaultSerializeValue(value.getWrapped(), tempGen);
tempGen.flush();
String json = stringWriter.toString();
String base64 = new String(Base64.getEncoder().encode(json.getBytes()));
gen.writeString(base64);
}
}
An example usecase would be:
final ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(new Base64WrapperSerializer());
mapper.registerModule(module);
final Foo foo = new Foo();
final Base64Wrapper<Foo> base64Wrapper = Base64Wrapper.of(foo);
final String base64Json = mapper.writeValueAsString(base64Wrapper);
This example can be found in this GitHub (branch: wrapper) repo, verifing you BASE64 String from your foo example with JUnit testing.
Instead of creating new object you may convert existing one into map. Like in the example below
import static java.nio.charset.StandardCharsets.UTF_8;
public class FooSerializer extends StdSerializer<Foo> {
public FooSerializer() {
super(Foo.class);
}
#Override
public void serialize(Foo foo, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) {
try {
ObjectMapper mapper = (ObjectMapper) jsonGenerator.getCodec();
var map = toMap(foo); // if you need class info for deserialization than use toMapWithClassInfo
String json = mapper.writeValueAsString(map);
jsonGenerator.writeString(Base64.getEncoder().encodeToString(json.getBytes(UTF_8)));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Map<String, Object> toMap(Object o) throws Exception {
Map<String, Object> result = new HashMap<>();
Field[] declaredFields = o.getClass().getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
result.put(field.getName(), field.get(o));
}
return result;
}
public static Map<String, Object> toMapWithClassInfo(Object obj) throws Exception {
Map<String, Object> result = new HashMap<>();
BeanInfo info = Introspector.getBeanInfo(obj.getClass());
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
Method reader = pd.getReadMethod();
if (reader != null)
result.put(pd.getName(), reader.invoke(obj));
}
return result;
}
}
I'm providing 2 ways of converting into map: with and without class info. Choose the one, applicable to your problem.
To serialize object jackson search #JsonValue method. You can add encodedJsonString method annotated by #JsonValue in Foo class.
Try with this:
#Getter
#Setter
public class Foo implements Serializable {
private static final long serialVersionUID = 1L;
public String field1;
public int field2;
#JsonValue
public String toEncodedJsonString() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new ObjectOutputStream(baos).writeObject(this);
return org.apache.commons.codec.binary.Base64.encodeBase64String(baos.toByteArray());
}catch (Exception ex){
}
return null;
}
}
I'm trying to convert the XML text to a Java object, but there is a number in the prQueryStatus XML attribute. The type of the Java field is an enum. Is there a way for JAXB to choose my enum?
Strxml:
<custom prQueryStatus="1" ></custom>
faulty row:
CustAttrPrQuery custom = (CustAttrPrQuery)XmlOperations.deserializeFromXML(CustAttrPrQuery.class, strXmlCustom);
XmlOperations.deserializeFromXML():
public static Object deserializeFromXML(Class obj, String strXml) {
Object result = null;
JAXBContext jaxbContext;
try {
jaxbContext = JAXBContext.newInstance(obj);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StringReader reader = new StringReader(strXml);
result = unmarshaller.unmarshal(reader);
return result;
} catch (JAXBException e) {
return new String("-3 JAXB deSerialize Error");
}
}
CustAttrPrQuery:
#XmlAccessorType(javax.xml.bind.annotation.XmlAccessType.FIELD)
#XmlRootElement(name = CustAttrPrQuery.RootElement)
public class CustAttrPrQuery {
public final static String RootElement = "custom";
#javax.xml.bind.annotation.XmlAttribute
private PrQueryStatus prQueryStatus = PrQueryStatus.NONE;
public PrQueryStatus getPrQueryStatus() {
return prQueryStatus;
}
public void setPrQueryStatus(PrQueryStatus prQueryStatus) {
this.prQueryStatus = prQueryStatus;
}
}
enum:
public enum PrQueryStatus {
NONE,
ACIK,
TUMU
}
You need to annotate your enum type with #XmlEnum
and its constants with #XmlEnumValue,
so that JAXB will know how to map from XML attributes ("0", "1", "2") to the enum constants (NONE, ACIK, TUMU):
#XmlEnum
public enum PrQueryStatus {
#XmlEnumValue("0") NONE,
#XmlEnumValue("1") ACIK,
#XmlEnumValue("2") TUMU
}
Using a Jaxb unmarshaller, I cannot achieve to load a XML content as a string.
Here is a running example of what I am trying to achieve.
public static class BarAdapter extends XmlAdapter<Object, String> {
#Override
public Object marshal(String v) throws Exception {
return null;
}
#Override
public String unmarshal(Object v) throws Exception {
return null; // what to do with the ElementNsImpl??
}
}
#XmlAccessorType(XmlAccessType.FIELD)
public static class Container implements Serializable {
#XmlAnyElement
#XmlJavaTypeAdapter(BarAdapter.class)
private String bar;
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
public static void main(String[] args) throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(Container.class);
String xml = "<foo><bar><name>Barry</name><surName>White</surName></bar></foo>";
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
InputStream is = new ByteArrayInputStream(xml.getBytes());
JAXBElement<Container> barWrapperElement = unmarshaller.unmarshal(new StreamSource(is), Container.class);
Container container = barWrapperElement.getValue();
System.out.println(container.getBar());
}
I would like to have into bar : <bar><name>Barry</name><surName>White</surName></bar>
I've tried to use the #XmlAnyElement but it gives a ElementNsImpl and I need a String.
If you have a better solution, please post. I am feeling that I am not doing it right.
I can transform ElementNsImpl into a String.
So :
#Override
public String unmarshal(Object obj) throws Exception {
// Be careful, affect a new string writer has to be done within your
// unmarshaller. If you do this here, it will partially unmarshall.
// I can povide more code upon request
StringWriter stringWriter = new StringWriter();
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
Document document = ((Element) obj).getOwnerDocument();
transformer .transform(new DOMSource(document), new StreamResult(stringWriter));
return stringWriter.toString();
}
I have a simpe XML that I want to unmarshall into a model class. I have annotated the class with JAXB annotations for defining the access type (FIELD):
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
#XmlAccessorType(XmlAccessType.FIELD)
public class DtoTest {
private String name;
public DtoTest() {}
public DtoTest(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "DtoTest [name=" + name + "]";
}
}
This is my main class where I run an unmarshal method against a simple XML saved in a String variable:
public class Test {
public static void main(String[] args) throws Exception {
Object obj = new DtoTest();
String testXML = "<dtoTest><name>example</name></dtoTest>";
obj = unmarshal(obj, testXML);
System.out.println(obj);
}
/* This is a generic unmarshall method which I've already used with success with other XML*/
public static <T> T unmarshal(T obj, String xml) throws Exception {
XMLInputFactory xif = XMLInputFactory.newFactory();
XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(xml));
Class<? extends Object> type = obj.getClass();
JAXBContext jc = JAXBContext.newInstance(type);
Unmarshaller unmarshaller = jc.createUnmarshaller();
obj = (T)unmarshaller.unmarshal(xsr, type).getValue();
xsr.close();
return obj;
}
}
Whenever I run the code I get the same output:
DtoTest [name=null]
I don't understand what I'm doing wrong.
I've just run your code on jdk1.7.0_67 and it works.
DtoTest [name=example]
Maybe you have some problem with included libraries? I've run it with just plain java.
What you have in your question runs perfectly fine for me. One optimization you could make to it is to create an StreamSource instead of an XMLStreamReader.
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import java.io.StringReader;
public class Test {
public static void main(String[] args) throws Exception {
Object obj = new DtoTest();
String testXML = "<dtoTest><name>example</name></dtoTest>";
obj = unmarshal(obj, testXML);
System.out.println(obj);
}
public static <T> T unmarshal(T obj, String xml) throws Exception {
StreamSource source = new StreamSource(new StringReader(xml));
Class<? extends Object> type = obj.getClass();
JAXBContext jc = JAXBContext.newInstance(type);
Unmarshaller unmarshaller = jc.createUnmarshaller();
obj = (T)unmarshaller.unmarshal(source, type).getValue();
return obj;
}
}
Debugging Tip
When unmarshalling is not working as expected, populate your JAXB model and marshal it to XML to see what the expected XML looks like.
I have a Maven & Spring based Java web application
In src/main/resources, I have one XML file.
sourceconfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<sourceConfig area="Defects">
<adapterObject>jAdapter</adapterObject>
<resultObject>jsonObject</resultObject>
</sourceConfig>
In I have a POJO for this SourceConfig.java
#XmlRootElement
public class SourceConfig {
String area;
String adapterObject;
String resultObject;
public String getArea() {
return area;
}
#XmlAttribute
public void setArea(String area) {
this.area = area;
}
public String getAdapterObject() {
return adapterObject;
}
#XmlElement
public void setAdapterObject(String adapterObject) {
this.adapterObject = adapterObject;
}
public String getResultObject() {
return resultObject;
}
#XmlElement
public void setResultObject(String resultObject) {
this.resultObject = resultObject;
}
}
I am able to parse the xml to object.
public class SourceAdapterConfig {
public SourceConfig getConfigObject() throws JAXBException, IOException {
JAXBContext jaxbContext = JAXBContext.newInstance(SourceConfig.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Resource resource=new ClassPathResource("sourceconfig.xml");
File file=resource.getFile();
SourceConfig sourceConfig = (SourceConfig) jaxbUnmarshaller.unmarshal(file);
return sourceConfig;
}
}
It is working fine.
But all are String. Some I want as object. For example, In XML I have mentioned <resultObject>jsonObject</resultObject>
I have a class com.myapp.config.JsonObject.java
So, instead of <resultObject>jsonObject</resultObject> If I mention class like this
<resultObject class="com.myapp.config.JsonObject">jsonObject</resultObject>
or some other way to mention class, I should be able to get a JsonObject object in my SourceConfig How can I do that?
use java reflection
Class theClass = Class.forName("com.example.Test");
Test testObject = (Test)theClass.newInstance();
This will create an instance of com.example.Test.
In your context,
public class SourceAdapterConfig {
private SourceConfig config;
private SourceConfig getConfigObject() throws JAXBException, IOException {
JAXBContext jaxbContext = JAXBContext.newInstance(SourceConfig.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Resource resource=new ClassPathResource("sourceconfig.xml");
File file=resource.getFile();
SourceConfig sourceConfig = (SourceConfig) jaxbUnmarshaller.unmarshal(file);
return sourceConfig;
}
public SourceAdapterConfig(){
config = getConfigObject();
}
public Object getAdapterObject(){
String adapterClassName = config.getAdapterObject();
Class theClass = Class.forName(adapterClassName);
return theClass.newInstance();
}
}
Usage:
SourceAdapterConfig config = new SourceAdapterConfig();
Object adapterObject = config.getAdapterObject();