I have been trying to create an XML using the simplexml library (v2.6.2)
http://simple.sourceforge.net/home.php
The XML I want to create has to hold an enum value, which should be case-sensitive. Following is the POJO :
package pojos;
public enum MyEnum {
NEW("new"),
OLD("old");
private final String value;
MyEnum(String v)
{
value = v;
}
public String value() {
return value;
}
public static MyEnum fromValue(String v) {
for (MyEnum c: MyEnum.values()) {
if (c.value.equals(v)) {
return c;
}
}
throw new IllegalArgumentException(v);
}
}
Following is the serializer code :
import java.io.File;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;
import pojos.MyEnum;
public class TestEnum {
/**
* #param args
* #throws Exception
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Serializer serializer = new Persister();
MyEnum example = MyEnum.NEW;
File result = new File("myenum.xml");
serializer.write(example, result);
}
}
The resultant output :
<myEnum>NEW</myEnum>
The desired output :
<myEnum>new</myEnum>
How should I proceed ? I cannot change the variable name in the enum as it happens to be the keyword "new" !
Thanks.
After some investigation of the source code, i have discovered that the library uses interface Transform to transform values to Strings. The default behavior of enum transformations is defined by class EnumTransform. In order to customize that, you can define you own Transform class. The following version of Transform implementation would call toString() instead of the default name() on the enum objects.
class MyEnumTransform implements Transform<Enum> {
private final Class type;
public MyEnumTransform(Class type) {
this.type = type;
}
public Enum read(String value) throws Exception {
for (Object o : type.getEnumConstants()) {
if (o.toString().equals(value)) {
return (Enum) o;
}
}
return null;
}
public String write(Enum value) throws Exception {
return value.toString();
}
}
Transform objects are returned from match method by objects of Matcher interface. There could be several Matcher objects. The library tries them one by one until it finds one that returns a non-null Transformer object. You can define your own Matcher object and pass it as argument to the constructor of Persister class. This object will get the highest priority.
Persister serializer = new Persister(new Matcher() {
public Transform match(Class type) throws Exception {
if (type.isEnum()) {
return new MyEnumTransform(type);
}
return null;
}
});
Finally, you wont forget to define a toString method on your enum classes. Then the combination of codes above will do you the work of encoding enum objects using their toString values.
You should override toString()
#Override
public String toString() {
return this.value.toLowerCase();
}
Then write results using
serializer.write(example.toString(), result);
I would look at the serializer code and undestand what that is doing, as you have not annotated any of your fields...which (according to their docs) should throw an an exception.
Related
I need to get the enum name based on value. I am given with enum class and value and need to pick the corresponding name during run time .
I have a class called Information as below.
class Information {
private String value;
private String type;
private String cValue;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getcValue() {
return cValue;
}
public void setcValue(String cValue) {
this.cValue = cValue;
}
public static void main(String args[]) {
Information inf = new Information();
inf.setType("com.abc.SignalsEnum");
inf.setValue("1");
}
}
class SignalEnum {
RED("1"), GREEN("2"), ORANGE("3");
private String sign;
SignalEnum(String pattern) {
this.sign = pattern;
}
}
class MobileEnum {
SAMSUNG("1"), NOKIA("2"), APPLE("3");
private String mobile;
MobileEnum(String mobile) {
this.mobile = mobile;
}
}
In run time i will come to know the enum name using the attribute type from the Information class and also i am getting the value. I need to figure out the corresponding enum to set the value for cValue attribute of Information class.
Just for example i have provided two enums like SignalEnum and MobileEnum but in my actual case i will get one among 100 enum types. Hence i dont want to check type cast. I am looking for some solution using reflection to se the cValue.
Here is a simple resolver for any enum class.
Since reflection operations are expensive, it's better to prepare all required data once and then just query for it.
class EnumResolver {
private Map<String, Enum> map = new ConcurrentHashMap<>();
public EnumResolver(String className) {
try {
Class enumClass = Class.forName(className);
// look for backing property field, e.g. "sign" in SignalEnum
Field accessor = Arrays.stream(enumClass.getDeclaredFields())
.filter(f -> f.getType().equals(String.class))
.findFirst()
.orElseThrow(() -> new NoSuchFieldException("Not found field to access enum backing value"));
accessor.setAccessible(true);
// populate map with pairs like ["1" => SignalEnum.RED, "2" => SignalEnum.GREEN, etc]
for (Enum e : getEnumValues(enumClass)) {
map.put((String) accessor.get(e), e);
}
accessor.setAccessible(false);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public Enum resolve(String backingValue) {
return map.get(backingValue);
}
private <E extends Enum> E[] getEnumValues(Class<E> enumClass) throws ReflectiveOperationException {
Field f = enumClass.getDeclaredField("$VALUES");
f.setAccessible(true);
Object o = f.get(null);
f.setAccessible(false);
return (E[]) o;
}
}
And here is simple JUnit test
public class EnumResolverTest {
#Test
public void testSignalEnum() {
EnumResolver signalResolver = new EnumResolver("com.abc.SignalEnum");
assertEquals(SignalEnum.RED, signalResolver.resolve("1"));
assertEquals(SignalEnum.GREEN, signalResolver.resolve("2"));
assertEquals(SignalEnum.ORANGE, signalResolver.resolve("3"));
}
#Test
public void testMobileEnum() {
EnumResolver mobileResolver = new EnumResolver("com.abc.MobileEnum");
assertEquals(MobileEnum.SAMSUNG, mobileResolver.resolve("1"));
assertEquals(MobileEnum.NOKIA, mobileResolver.resolve("2"));
assertEquals(MobileEnum.APPLE, mobileResolver.resolve("3"));
}
}
And again for performance sake you can also instantiate these various resolvers once and put them into a separate Map
Map<String, EnumResolver> resolverMap = new ConcurrentHashMap<>();
resolverMap.put("com.abc.MobileEnum", new EnumResolver("com.abc.MobileEnum"));
resolverMap.put("com.abc.SignalEnum", new EnumResolver("com.abc.SignalEnum"));
// etc
Information inf = new Information();
inf.setType("com.abc.SignalsEnum");
inf.setValue("1");
SignalEnum red = (SignalEnum) resolverMap.get(inf.getType()).resolve(inf.getValue());
I am attempting to deserialize a jackson-serialized Google Ads sdk object. In particular, I am running into issues in instantiating specific classes which behave like enums, for example :
public class CampaignStatus implements Serializable {
private String _value_;
private static HashMap _table_ = new HashMap();
public static final String _UNKNOWN = "UNKNOWN";
public static final String _ENABLED = "ENABLED";
public static final String _PAUSED = "PAUSED";
public static final String _REMOVED = "REMOVED";
public static final CampaignStatus UNKNOWN = new CampaignStatus("UNKNOWN");
public static final CampaignStatus ENABLED = new CampaignStatus("ENABLED");
public static final CampaignStatus PAUSED = new CampaignStatus("PAUSED");
public static final CampaignStatus REMOVED = new CampaignStatus("REMOVED");
private static TypeDesc typeDesc = new TypeDesc(CampaignStatus.class);
protected CampaignStatus(String value) {
this._value_ = value;
_table_.put(this._value_, this);
}
public String getValue() {
return this._value_;
}
public static CampaignStatus fromValue(String value) throws IllegalArgumentException {
CampaignStatus enumeration = (CampaignStatus)_table_.get(value);
if (enumeration == null) {
throw new IllegalArgumentException();
} else {
return enumeration;
}
}
public static CampaignStatus fromString(String value) throws IllegalArgumentException {
return fromValue(value);
}
public boolean equals(Object obj) {
return obj == this;
}
public int hashCode() {
return this.toString().hashCode();
}
public String toString() {
return this._value_;
}
public Object readResolve() throws ObjectStreamException {
return fromValue(this._value_);
}
public static Serializer getSerializer(String mechType, Class _javaType, QName _xmlType) {
return new EnumSerializer(_javaType, _xmlType);
}
public static Deserializer getDeserializer(String mechType, Class _javaType, QName _xmlType) {
return new EnumDeserializer(_javaType, _xmlType);
}
public static TypeDesc getTypeDesc() {
return typeDesc;
}
static {
typeDesc.setXmlType(new QName("https://adwords.google.com/api/adwords/cm/v201809", "CampaignStatus"));
}
}
When a Campaign object is serialized (which contains a CampaignStatus as defined above), the JSON looks like this:
"status":{"value":"ENABLED"}
The deserializer throws a mismatched input exception when trying to write a JSON campaign to a Campaign object. Since the objects are owned by Google, I can't modify the existing classes or add annotations. My solution needs to work for 250+ classes that follow this pattern, so individually wrapping or extending these isn't a feasible solution. Additionally, I will have many different stakeholders serializing these objects, so modifying how they are serialized is also not a useable solution.
What I need is some way to indicate to the deserializer that when it comes across a situation like this, it should look for the fromValue method and use that. I'm ok with explicitly stating what json keys/values would need to use such a method; I just need a more dynamic way of modifying the serialization than extending the class or adding annotations.
You can indicate factory method using #JsonCreator annotation - it is going to be used by Jackson to perform deserialization.
In your case it would look something like this:
#JsonCreator
public static CampaignStatus fromValue(#JsonProperty("value") String value) throws IllegalArgumentException {
CampaignStatus enumeration = (CampaignStatus)_table_.get(value);
if (enumeration == null) {
throw new IllegalArgumentException();
} else {
return enumeration;
}
}
I am a bit confused with what exactly you are trying to achieve but feel free to put any logic that fulfill you requirements in the method above.
So I didn't find the best possible solution, but I'll share the best I could come up with. I implemented a new generic deserializer extending JsonDeserializer, and used reflection in the deserialize method to invoke the .fromValue method. I then used reflection to search the directories for all classes, and search each class for a matching .fromValue method. Every time I successfully find a class that follows this pattern, I register it with the mapper using a new generic deserializer for the corresponding class. It's a bit costly, but I only register the deserializers at instantiation so it's ok that it's a complex.
In fasterxml, after deserialization json, if enum (with JsonFormat.Shape.OBJECT) is first property in class, other fields are null.
Why enum should be last declared property in class to deserialize other fields properly?
Maybe this could be a bug in fasterxml?
Example class MyClass:
public class MyClass {
// >>>
// >>> element field is null after deserialization
// >>>
private MyEnum option; // first
private String element; // --> null
// >>>
// >>> correctly deserialized if enum is last in order
// >>>
// private String element; // --> "elem"
// private MyEnum option; // last
public MyEnum getOption() {
return option;
}
public void setOption(MyEnum option) {
this.option = option;
}
public String getElement() {
return element;
}
public void setElement(String element) {
this.element = element;
}
}
Example enum MyEnum:
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum MyEnum {
FIRST;
#JsonProperty
public String getOption() {
return name();
}
#JsonCreator
public static MyEnum forValue(String option) {
return FIRST;
}
}
Example main test class Main:
public class Main {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MyClass myClass = new MyClass();
myClass.setElement("elem");
myClass.setOption(MyEnum.FIRST);
String serialized = mapper.writer().withDefaultPrettyPrinter().writeValueAsString(myClass);
System.out.println(String.format("serialized - %s", serialized));
MyClass deserialized = mapper.readValue(serialized, MyClass.class);
String deserializedResult = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(deserialized);
System.out.println(String.format("deserialized - %s", deserializedResult));
}
}
Output showing field is null after deserialization:
serialized - {
"option" : {
"option" : "FIRST"
},
"element" : "elem"
}
deserialized - {
"option" : {
"option" : "FIRST"
},
"element" : null
}
Output after fixing order (uncommented lines in MyClass):
serialized - {
"element" : "elem",
"option" : {
"option" : "FIRST"
}
}
deserialized - {
"element" : "elem",
"option" : {
"option" : "FIRST"
}
}
I couldn't tell you if it's a bug, you can debug and step through the code to understand how Jackson "fails" in this scenario. Your use of FAIL_ON_UNKNOWN_PROPERTIES hides the problem, which is using String as the parameter type of your forValue factory method. In short, Jackson gets "stuck" in traversing the tokens of the JSON content.
To fix it properly, ie. not rely on order, you have a couple of options. First, get rid of the JsonFormat.Shape.OBJECT shape for serializing the enum type and its corresponding #JsonCreator. The default serialization/deserialization for an enum is to use its name anyway.
Second, if you really want to keep the OBJECT shape, you'll need to change your #JsonCreator method to receive an ObjectNode, since that's what the JSON contains, not a String. From there, you can perform the deserialization yourself (assuming you have more enum constants)
#JsonCreator
public static MyEnum forValue(ObjectNode object) {
return MyEnum.valueOf(object.get("option").asText());
}
i am trying to map String to enum Object using Jackson ObjectMapper.readValue(String,Class) API, problem is Lets SAY my json string contains a Task Object with Action enum as below
public enum Action {
#XmlEnumValue("Add")
ADD("Add"),
#XmlEnumValue("Amend")
AMEND("Amend"),
#XmlEnumValue("Delete")
DELETE("Delete"),
#XmlEnumValue("Pending")
PENDING("Pending");
private final String value;
Action(String v) {
value = v;
}
public String value() {
return value;
}
public static Action fromValue(String v) {
for (Action c: Action.values()) {
if (c.value.equals(v)) {
return c;
}
}
throw new IllegalArgumentException(v);
}
}
and the jason string will be like this "{"action":"Add"}" then ObjectMapper.readValue(jsonString, Task.Class) throws
org.codehaus.jackson.map.deser.StdDeserializationContext.weirdStringException(StdDeserializationContext.java:243) for Action Add because it cant convert this Enum.
I tried adding custom Desiserializer, But EnumDeserializer getting called anyway. any ideas?
All objects are JAXB generated, so annotations not possible.
Thanks for the help
Have you tried:
new ObjectMapper().setAnnotationIntrospector(new JaxbAnnotationIntrospector()).readValue()
we are creating a JSON REST client application that has to communicate with a service written in C#.
Most things like difference in dates etc are solved pretty easily with the FlexJson library.
But one thing doesn't: Enum values that are sent as an integer, which is the value received from the service, and that have to be mapped to their Java Enum value.
The Java enum is ok, we can convert integers to the enum as long as the value exists of course.
But we do not succeed to get the Flexjson to convert the value.
One of the Enums is called FormState
We wrote a custom Transformer let's call it OurEnumTransformer which extends AbstractTransformer and implements ObjectFactory.
Upon deserialization we add the .use(Enum.class, OurEnumTransformer), if we don't we get an error like:
Don't know how to convert 4 to enumerated constant of FormState
which makes sense as it is an integer and not a name of an enum value
But we add the .use(...) we keep getting an error on deserialization:
FormState lacks a no argument constructor. Flexjson will instantiate any protected, private, or public no-arg constructor.
But it does actually have a private parameterless constructor.
Another thing is that a breakpoint that is set in the OurEnumTransformer is never hit.
So can anyone help me why .use(Enum.class, OurEnumTransformer) does not work with an enum that has integer values?
The code of the enum and OurEnumTransformeris below
public enum FormState {
None(0),
EditMode(1),
SignedBySender(2),
AddedToRide(4),
SignedByTransporter(8),
SignedByReceiver(16),
DLT_UNKNOWN();
private int value;
private FormState() {
this.value= -1;
}
private FormState(int value) {
this.value= value;
}
public int getValue()
{
return value;
}
private static final Map<Integer, FormState> intToTypeMap = new HashMap<Integer, FormState>();
static
{
for(FormState type: FormState.values())
{
intToTypeMap.put(type.value, type);
}
}
public static FormState fromInt(int i) {
FormState type = intToTypeMap.get(Integer.valueOf(i));
if (type == null)
return FormState.DLT_UNKNOWN;
return type;
}
}
and the factory
public final class OurEnumTransformer extends AbstractTransformer implements
ObjectFactory {
#SuppressWarnings("rawtypes")
#Override
public Object instantiate(ObjectBinder context, Object value, Type targetType,
Class targetClass)
{
if(targetClass.equals(FormState.class))
return FormState.fromInt((Integer)value);
else if(targetClass.equals(TrajectState.class))
return TrajectState.fromInt((Integer)value);
else
throw new JSONException(String.format("%s: Don't know how to convert %s to enumerated constant of %s",
context.getCurrentPath(), value, targetType));
}
#Override
public void transform(Object arg0) {
// TODO Auto-generated method stub
}
}
finally the calling code:
List<JsonTrajectModel> result = null;
JSONDeserializer<List<JsonTrajectModel>> deser = new JSONDeserializer<List<JsonTrajectModel>>();
result = deser
.use(Date.class, dateTransformer)
.use("values", JsonTrajectModel.class)
.use(Enum.class, enumTransformer)
.deserialize(jsonData);
In the last code block jsonData is a valid JSON string and enumTransformer is the OurEnumTransformer instance.
A final remark, the dateTransformer used in this code does do its work.
Any help is appreciated.