#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class ItemSubstitutionRequestDTO {
public ItemSubstitutionRequestDTO()
{
}
private List<Map<String,Integer>> substituteFor=new ArrayList<Map<String,Integer>>();
private String orderId;
public List<Map<String,Integer>> getSubstituteFor()
{
return substituteFor;
}
public void setSubstituteFor(List<Map<String,Integer>> substituteFor)
{
this.substituteFor = substituteFor;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
}
Final result ERROR:
java.util.Map is an interface, and JAXB can't handle interfaces.
I can't get JaxB to be able to marshall/unmarshall instances of Map.I tried other annotation also and found this is one of the possible way to solve the above error but nothing is woking.
Below is the input json which is coming from UI side
{ "itemSubstitutionRequestDTO": { "substituteFor": [{"41712":2}],
"orderId": "1073901", } }
You didn't write how your XML content within the
<substituteFor> element would look like.
Therefore I assume something like this:
<itemSubstitutionRequestDTO>
<substituteFor>
<item>
<key>x</key>
<value>23</value>
</item>
<item>
<key>y</key>
<value>3</value>
</item>
</substituteFor>
<orderId>abc</orderId>
</itemSubstitutionRequestDTO>
As the JAXB error message already told you,
it can't handle types with an interface between the < >,
like for example your List<Map<String,Integer>>.
However it can handle types with a normal class between < >,
like List<SubstitutionMap>.
So the first step is to rewrite your ItemSubstitutionRequestDTO class
so that it does not use List<Map<String,Integer>>, but instead List<SubstitutionMap>.
You need to write the SubstitutionMap class (not an interface) by yourself.
But it can be extremely simple:
public class SubstitutionMap extends HashMap<String, Integer> {
}
Now JAXB doesn't throw an error anymore, but it still doesn't know how to marshal/unmarshal a SubstitutionMap.
Therefore you need to write an XmlAdapter for it.
Let's call it SubstitutionMapAdapter.
To make JAXB aware of this adapter, you need to annotate the substituteFor
property in your ItemSubstitutionRequestDTO class with:
#XmlJavaTypeAdapter(SubstitutionMapAdapter.class)
The adapter's job is to do the actual conversion from SubstitutionMap
to an array of SubstitutionMapElements and vice versa.
Then JAXB can handle the SubstitutionMapElement array by itself.
public class SubstitutionMapAdapter extends XmlAdapter<SubstitutionMapElement[], SubstitutionMap> {
#Override
public SubstitutionMap unmarshal(SubstitutionMapElement[] elements) {
if (elements == null)
return null;
SubstitutionMap map = new SubstitutionMap();
for (SubstitutionMapElement element : elements)
map.put(element.getKey(), element.getValue());
return map;
}
#Override
public SubstitutionMapElement[] marshal(SubstitutionMap map) {
// ... (left to you as exercise)
}
}
The class SubstitutionMapElement is just a simple container for a key and a value.
#XmlAccessorType(XmlAccessType.FIELD)
public class SubstitutionMapElement {
private String key;
private int value;
// ... constructors, getters, setters omitted here for brevity
}
Related
I have 2 implementations of Value interface, RangeValue, FileValue.
RangeValue looks like below:
public class RangeValue implements Value {
private int min;
private int max;
public RangeValue(int min, int max) {
this.min = min;
this.max = max;
}
public int getMin() {
return min;
}
public int getMax() {
return max;
}
}
FileValue looks like below:
public class FileValue implements Value {
private String contentType;
private String value;
public FileValue(String contentType, String value) {
this.contentType = contentType;
this.value = value;
}
public String getContentType() {
return contentType;
}
public String getValue() {
return value;
}
}
the json for RangeValue looks like :
{
"min": 200,
"max": 300
}
The json for FileValue looks:
{
"contentType": "application/octet-stream",
"value": "fileValue"
}
Now I want the RequestType parameter for these json to be of type Value only, I can't change the JSON files i.e. the json would look like the same and user should use the same JSON in request body as stated above.
I solved this by using #JsonTypeInfo & #JsonSubTypes by adding extra attributes to the above JSON i.e. type but the spec doesn't allow me to add that.
How can the appropriate concrete class could be instantiated based on the JSON above without altering?
Option 1: custom deserializer. Algorithm can be as follows:
Parse to JsonNode.
Use the properties in the node to find the correct class to deserialize into.
Convert the node to instance of the actual class.
Simplified example:
public class ValueDeserializer extends StdDeserializer<Value> {
public ValueDeserializer() {
super(Value.class);
}
#Override
public Value deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode root = parser.readValueAsTree();
if (root instanceof ObjectNode objectNode) {
JsonNode valueNode = objectNode.get("somePropertyName");
Class<? extends Value> clazz = valueNode == null ? RangeValue.class : FileValue.class;
return context.readTreeAsValue(objectNode, clazz);
}
throw new JsonParseException(parser, "not an object");
//handling the case, when json is json array
//or something else which can't be deserialized into object
}
}
Register the deserializer with JsonDeserialize on the interface:
#JsonDeserialize(using = ValueDeserializer.class)
Put the same annotation on RangeValue and FileValue, without specifying a deserializer, otherwise you will get StackOverflowError.
Option 2: use JsonTypeInfo.Id.DEDUCTION
#JsonSubTypes({
#JsonSubTypes.Type(FileValue.class),
#JsonSubTypes.Type(RangeValue.class)
})
#JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
public interface Value {
}
Jackson will deduce the correct class using the property names. Keep in mind exception will be thrown if it fails deduction.
Means that no serialized typing-property is used. Types are deduced based on the fields available. Deduction is limited to the names of fields (not their values or, consequently, any nested descendants). Exceptions will be thrown if not enough unique information is present to select a single subtype.
If deduction is being used annotation properties visible, property and include are ignored.
I have the following data model with custom attributes:
class Foo {
private Long id;
private Set<AdditionalAttribute> attributes;
}
class AdditionalAttribute {
private Key key;
private String value;
}
class Key {
private String name;
private Class<?> type;
}
My model produces this json:
{"id":123, "attributes": [{"key1":12345}, {"key2":"value2"}]}
My expected json is:
{"id":123, "key1":12345, "key2":"value2"}
How can I achieve a such serialization / deserialization using graphql spqr?
FYI, currently I can do it in REST API with jackson (BeanSerializerModifier for serialization and #JsonAnySetter for deserialization) as follow:
// Serialization using BeanSerializerModifier
class FooModifier 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);
if (Foo.class.isAssignableFrom(beanDesc.getBeanClass()) && "attributes".equals(writer.getName())) {
beanProperties.set(i, new FooAttributesWriter(writer));
}
}
return beanProperties;
}
}
class FooAttributesWriter extends BeanPropertyWriter {
public HasAttributesWriter(BeanPropertyWriter w) {
super(w);
}
#Override
public void serializeAsField(Object bean, JsonGenerator gen,
SerializerProvider prov) throws Exception {
if(Foo.class.isAssignableFrom(bean.getClass())) {
Set<AdditionalAttribute> set = ((Foo) bean).getAttributes();
for (AdditionalAttribute a : set) {
gen.writeStringField(a.getKey().getName(), a.getValue());
}
}
}
}
// Deserilization using #JsonAnySetter
class Foo {
private Long id;
private Set<AdditionalAttribute> attributes;
// Deserialization of custom properties
#JsonAnySetter
public void set(String name, Object value) {
attributes.add(new AdditionalAttribute(buildKey(name,value), value));
}
}
The problem here is not JSON (de)serialization. With GraphQL, the shape of all your inputs and outputs is defined by the schema, and the schema can not normally have dynamic parts (object types where the fields are unknown ahead of time). Because your Set<AdditionalAttribute> can contain anything at all at runtime, it means your Foo type would have to have unknown fields. This is highly antithetical to how GraphQL is designed.
The only way to achieve a dynamic structure is to have an object scalar which effectively is a JSON blob that can not be validated, or sub-selected from. You could turn Foo into such a scalar by adding #GraphQLScalar to it. Then all input would be valid, {"id":123, "key1":12345 "key2":"value2"} but also {"whatever": "something"}. And it would be your logic's job to ensure correctness. Additionally, if you ever return Foo, the client would not be able to sub-select from it. E.g. {foo} would be possible but {foo { id }} would not, because the schema would no longer know if the id field is present.
To recap, you options are:
Leaving it as it is (the dynamic stuff is a list nested under attributes)
Turning Set<AdditionalAttribute> into a type (a new class or EnumMap) with known structure with all the possible keys as fields. This is only possible if the keys aren't totally dynamic
Making the whole enclosing object an object scalar by using #GraphQLScalar
Thanks a lot for your time and the proposed options.
Currently, we have found another way (maybe option 4 :) ) to generate a "similar" json to the expected output (We lost the type information in the generated output, but we have another logic that helps us to retrieve the type).
Here an example :
class Foo {
private Long id;
private Set<AdditionalAttribute> attributes;
#GraphQLQuery
public String get(#GraphQLArgument(name = "key") String key) {
for (AdditionalAttribute a : attributes) {
if (a.getConfigurationKey().getKey().equalsIgnoreCase(key)) {
return a.getAttributeValue();
}
}
return null;
}
and we can sub-select Foo as follow:
foo {
id
key1: get(key: "key1")
key2: get(key: "key2")
}
And this return
{"id":123, "key1":"12345", "key2":"value2"}
I have a data structure in Java that I am populating via different methods. One method populates it from an API, another method populates it from parsing some HTML, another populates it a different way, etc. Basically, a method for every data source that could populate it. What I'm wondering is, what design patterns are available in Java for this? What's the best/cleanest OOP approach to this problem?
E.g.,
public class Data {
private String foo;
private List<String> bar;
private Map<String, Integer> baz;
public Data (String foo, List<String> bar, Map<String, Integer baz) {
this.foo = foo;
this.bar = bar;
this.baz = baz;
}
// Setters and Getters here, etc
}
public class FacebookParser {
private Document dom;
public static Data parse(Document dom) {
// Parse document
// Create Data object
return Data;
}
}
public class TwitterParser {
private Document dom;
public static Data parse(Document dom) {
// Parse Twitter
Data d = new Data(stuff from twitter);
return d;
}
}
You want a Data and it is represented in different forms. The part that you are interested in should be defined in an abstract way. So making the Data an interface is a good point for starting.
public interface Data {
String getFoo();
List<String> getBar();
Map<String, Integer> getBaz();
}
This data is obtained from different providers. The common thing is we need someone to provide Data. In the end, the only thing we are interested in is the Data itself, not how it is parsed or provided. So we need a simple DataProvider interface.
public interface DataProvider {
Data createData();
}
Now we can implement the provider classes those know how to fetch, parse, process etc. the data. Provider classes should not be dealing with how to convert the provider specific data into our common Data interface. They are only responsible for creating a Data implementation that they know.
public class FacebookDataProvider implements DataProvider {
public Data createData() {
FacebookSpecificInfo x = ...
FacebookData data = new FacebookData();
// Note that this class does not know anything about foo, bar and baz.
// We are still Facebook context.
data.setName(x.getName());
data.setValues(x.getValues());
data.setHeaders(x.getHeaders());
return data;
}
}
class FacebookData implements Data {
private String name;
private List<String> values;
private Map<String, Integer> headers;
void setName(String name) { this.name = name; }
void setValues(String values) { this.values = values; }
void setHeaders(String headers) { this.headers = headers; }
// This is the part where we switch the context and convert
// Facebook specific data into our expected Data
// ie. Facebook's name field corresponds my foo field.
public String getFoo() { return name; }
public List<String> getBar() { return values; }
public Map<String, Integer> getBaz() { return headers; }
}
What you can do is have a separate class for setting the values of the Data class.
You can have something like this :
public class DataPopulator{
public void setTwitterData(Data d){
//your data
}
public void setFacebookData(Data d){
//your data
}
}
This is something similar to Adapter Design pattern, though not exactly same.
You can have a look at it here.
I am trying to unmarshall an ArrayList of a generic class called Key.
the Key has setValue() method which recieves a generic parameter.
Key class
#XMLRootElement(name = "Key")
public class Key<T>{
#XMLElement(name = "Key")
public setKey(T value){
this.value = value
}
}
The specific ArrayList
#XMLElementWrapper(name = "Keys")
#XMLElement(name = "Key")
public setKeys(ArrayList<Key> keys){
this.keys = keys;
}
This part of the XML file
<Keys>
<Key>2</Key>
</Keys>
Running the code would create the ArrayList and WILL have a single Key object in it.
But the Key would be Null.
(Ive tried debugging and could notice that it does not call the setKey() setter of the class)
Anything to do with the fact it's generic?
Thanks in advance.
EDIT
In the past day ive debugged this alot, i can say now that the problem is with the fact that after instantiating the ArrayList, while creating each Key per Key Tag in the XML, the unmarshaller uses the Key's empty constructor and just NEVER calls the setter of it, therefore i have an ArrayList containing Keys which their 'value' data member is null.
Can anyone please explain what am I doing wrong? Why does the setter not getting called?
Thank you.
You are probably out of luck. How is the unmarshaller supposed to know that 2 is an integer and not a double or a long or a timestamp or some other class with a custom adapter that can parse 2 into itself.
The annotations you want are basically below (minus the #XmlJavaTypeAdapter which I will explain in a moment) but if you try and run that code without the adapter you will get a NullPointerException because JAXB cannot handle the #XmlValue annotation on an Object (which is how it treats T). The reason JAXB cannot handle it is because it has no way of knowing what the object is.
Now, if you have your own custom rules for determining the type of T (e.g. when coming from XML T is always an Integer or T is an Integer if it doesn't contain a '.' and a Double otherwise) then you can implement your own logic using an adapter which is what I've demonstrated below (I used the second rule).
#XmlRootElement(name="root")
public class SO {
private List<Key<?>> keys;
#XmlElementWrapper(name="Keys")
#XmlElement(name="Key")
public void setKeys(List<Key<?>> keys) {
this.keys = keys;
}
public List<Key<?>> getKeys() {
return keys;
}
#XmlType
public static class Key<T> {
private T val;
#XmlValue
#XmlJavaTypeAdapter(ToStringAdapter.class)
public void setKey(T val) {
this.val = val;
}
public String toString() {
return "Key(" + val + ")";
}
}
public static class ToStringAdapter extends XmlAdapter<String, Object> {
#Override
public Object unmarshal(String v) throws Exception {
if(v.contains(".")) {
return Double.parseDouble(v);
} else {
return Integer.parseInt(v);
}
}
#Override
public String marshal(Object v) throws Exception {
return v.toString(); //Will never be called anyway so you could also throw an exception here
}
}
private static final String XML_INT = "<root><Keys><Key>2</Key></Keys></root>";
private static final String XML_DOUBLE = "<root><Keys><Key>2.7</Key></Keys></root>";
public static void main(String [] args) throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(Key.class, SO.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
SO so = (SO) unmarshaller.unmarshal(new StringReader(XML_INT));
System.out.print(so.keys);
System.out.println(" " + so.keys.get(0).val.getClass().getSimpleName());
so = (SO) unmarshaller.unmarshal(new StringReader(XML_DOUBLE));
System.out.print(so.keys);
System.out.println(" " + so.keys.get(0).val.getClass().getSimpleName());
}
}
I have the following Enum:
public enum MyState {
Open("opened"),
Close("closed"),
Indeterminate("unknown");
private String desc;
private MyState(String desc) {
setDesc(desc);
}
public String getDesc() {
return this.desc;
}
private void setDesc(String desc) {
this.desc = desc;
}
}
I am trying to write an XStream Converter that will know to map back a JSON element "mystate" to a MyState instance.
"someJson": {
"object1": {
"mystate": closed
}
}
This should produce, amongst other objects (someJson and object1) a MyState.Close instance. I've started the Converter, but haven't gotten very far:
public class MyStateEnumConverter implement Converter {
#Override
public boolean canConvert(Class clazz) {
return clazz.equals(MyState.class);
}
#Override
public void marshal(Object value, HierarchialStreamWriter writer, MarshallingContext context) {
??? - no clue here
}
#Override
public Object unmarshal(HierarchialStreamReader reader, UnmarshallingContext context) {
??? - no clue here
}
}
Then, to create the mapper and use it:
XStream mapper = new XStream(new JettisonMappedXmlDriver());
mapper.registerConverter(new MyStateEnumConverter);
SomeJson jsonObj = mapper.fromXML(jsonString);
// Should print "closed"
System.out.println(jsonObject.getObject1().getMyState().getDesc());
How can I implement marshal and unmarshal so thatI get the desired mapping? Thanks in advance!
You can accomplish this by doing 2 things:
Adding a lookup method as well as a toString() override to your enum (MyStateEnum); and
Extending XStream's AbstractSingleValueConverter instead of implementing Converter
MyStateEnum:
public enum MyStateEnum {
// Everything you had is fine
// But now, add:
public static MyStateEnum getMyStateByDesc(String desc) {
for(MyStateEnum myState : MyStateEnum.values())
if(myState.getDesc().equals(desc))
return myState;
return null;
}
#Override
public String toString() {
return getDesc();
}
}
MyStateEnumConverter:
public class MyStateEnumConverter extends AbstractSingleValueConverter {
#Override
public boolean canConvert(Class clazz) {
return clazz.equals(MyStateEnum.class);
}
#Override
public Object fromString(String parsedText) {
return MyStateEnum.getMyStateByDesc(parsedText);
}
}
By adding getMyStateByDesc(String) to your enum, you now have a way to look up all the various enumerated values from the outside, by providing a desc string. The MyStateEnumConverter (which extends AbstractSingleValueConverter) uses your toString() override under the hood to associate aMyStateEnum instance with a text string.
So when XStream is parsing the JSON, it sees a JSON object of, say, "opened", and this new converter knows to pass "opened" into the converter's fromString(String) method, which in turn uses getMyStateByDesc(String) to lookup the appropriate enum instance.
Don't forget to register your converter with your XStream instance as you already showed in your original question.
You can use the EnumToStringConverter
Documentation
Example
#XStreamConverter(EnumToStringConverter.class)
public enum MyStateEnum {
enter code here
...
Use xstream.autodetectAnnotations(true)
Why are you using xstream for json support? You have a couple of other libraries specialized in json and that do it well. Also closed without quotes is not valid json.
Try for example Genson, it will work out of the box.
The values in the json stream would be "Close", "Indeterminate", etc and when deserializing it will produce the correct enum.
class SomeObject {
private MyState state;
...
}
Genson genson = new Genson();
// json = {"state" : "Indeterminate"}
String json = genson.serialize(new SomeObject(MyState.Indeterminate));
// deserialize back
SomeObject someObject = genson.deserialize(json, SomeObject.class);
// will print unknown
System.out.println(someObject.getDesc());