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());
}
}
Related
I am getting alert in Checkmarx scan saying Unsafe object binding in the saveAll() call.
The exact words in checkmarx are -
The columnConfigSet at src\main\java\com\ge\digital\oa\moa\controller\ConfigController.java in line 45 may unintentionally allow setting the value of saveAll in setColumnsConfig, in the object src\main\java\com\ge\digital\oa\moa\service\ConfigService.java at line 170.
Any idea how to rewrite the code , so that the checkmarx stops complaining.
My code:
#PutMapping("/columns")
#ResponseStatus(OK)
public void setColumnsConfig(#RequestBody(required=true) ColumnConfigSetDto columnConfigSet) {
service.setColumnsConfig(columnConfigSet);
}
public void setColumnsConfig(ColumnConfigSetDto columnConfigSet) {
String userId = columnConfigSet.getUserId();
String viewName = columnConfigSet.getViewName();
List<ColumnConfig> configs = new ArrayList<>();
for (ColumnConfigDto colConfig : columnConfigSet.getColumns()) {
// build a db config row only for the visibility property for now
ColumnConfigId confId = new ColumnConfigId();
confId.setUserId(userId);
confId.setViewName(viewName);
confId.setKey(colConfig.getKey());
confId.setProperty("visible");
ColumnConfig conf = new ColumnConfig();
conf.setColumnConfigId(confId);
conf.setValue(colConfig.getIsVisible() ? "true" : "false" );
configs.add(conf);
}
if (!configs.isEmpty()) {
configRepo.saveAll(configs);
}
}
Below are my DTO Objects which is used in this code :
#Getter
#Setter
public class ColumnConfigSetDto {
#JsonProperty("userId")
private String userId;
#JsonProperty("viewName")
private String viewName;
#JsonProperty("columns")
private List<ColumnConfigDto> columns;
}
Below are my DTO code which is used in this
#Getter
#Setter
public class ColumnConfigDto {
#JsonProperty("key")
private String key;
#JsonProperty("label")
private String label;
#JsonProperty("isVisible")
private Boolean isVisible;
#JsonProperty("position")
private Integer position;
#JsonProperty("isSortable")
private Boolean isSortable;
#JsonProperty("isHideable")
private Boolean isHideable;
}
Here is my solution for Unsafe object binding reported by cherkmarx in Java.
It's not a graceful approach and only fix this vulnerability.
Remove all setter methods for boxed fields in each requestbody bean.
Since #JsonProperty could support deserialization capbility, no need to add setter manually.
If you need setter for request body bean indeed, you can use reflaction way instead.
FieldUtils.writeField(columnConfigDto , "isVisible", true, true);
public class ColumnConfigDto {
// Ensure #JsonProperty existed on each field
#JsonProperty("key")
private String key;
#JsonProperty("isVisible")
private Boolean isVisible;
#JsonProperty("list")
private List list;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Boolean getVisible() {
return isVisible;
}
// Remove boxed type field
// public void setVisible(Boolean visible) {
// isVisible = visible;
// }
public List getList() {
return list;
}
// Remove boxed type field
// public void setList(List list) {
// this.list = list;
// }
}
this issue occurs due to #RequestBoby as per spring documentation but there is no issue for #RequestParam. if we bind request body to object without #RequestBody, this issue is not occurred.
HttpServletRequest request;
mapper.readValue(request.getInputStream(), Product.class);
The error is also thrown if data is set to an object annotated with #RequestBody.
requestBodyVariable.setAdditionalValue(valueFromRequestParamOrPathVariable);
// This setter call should not be used
Instead, use a user-defined variable for storing the value from request param, header or path variable in its place:
service.callServiceMethod(requestBodyVariable, valueFromRequestParamOrPathVariable);
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 am trying to convert the following JSON structure (part of a larger JSON object) to a POJO but getting the exception copied below (using Java/Jackson).
JSON
"outputKeys":
{"ABC":"gGyIioUr4Jfr5QiCm6Z==",
"DEF":"RxHfNyD2JyPOpG5tv3Jaj5g=="}
Java class
private class OutputKeys {
private String key;
private String value;
public OutputKeys(String key, String value) {
this.key = key;
this.value = value;
}
}
&
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(jsonString, Test.class);
exception:
no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?
Test class has the OutputKeys as an attribute.
Any suggestions would be welcome. I have tried using a List of OutputKeys as well .
Update:
I have tried the following without success:
class OutputKeys {
public Map<String, String> keys;
///with constructor/setter/getters
}
&
class OutputKeys {
public List<OutputKey> keys;
///with constructor/setter/getters
public class OutputKey {
Map<String, String> outputs = new HashMap<>();
// tried this too:
// String key
//String value
}
You require below mentioned single class only, containing
All keys(ABC and DEF)
getters/setters
toString() which you'll use interact with JSON.
public class OutputKeys
{
private String ABC;
private String DEF;
public String getABC ()
{
return ABC;
}
public void setABC (String ABC)
{
this.ABC = ABC;
}
public String getDEF ()
{
return DEF;
}
public void setDEF (String DEF)
{
this.DEF = DEF;
}
#Override
public String toString()
{
return "ClassPojo [ABC = "+ABC+", DEF = "+DEF+"]";
}
}
Let me know if you require more details.
Since the keys were dynamic, I ended up deserializing the data using the iterator on the JsonNode:
jsonNode.get("outputKeys").iterator()
& then getting the relevant dynamic key information via the iterator.
I needed a similar tool for NodeJS. So that I can write tests on parts of a bigger model that was serialized (JSON).
So, if I need only "ABC":"gGyIioUr4Jfr5QiCm6Z==" or "XYZ":{"Hello": "My String", "Content": [1,2,3]}, the only property I care to test at the moment is:
var sutXYX = { Hello: "My String", Content: [ 1, 2, 2]};
I wrote this tool as a utility https://github.com/whindes/PojoScriptifyFromJSON
I have the following XML structure:
<key>
<element>
someValue
</element>
<!-- lots of other elements which should be deserialized into the class -->
<other>
someOtherValue
</other>
</key>
And i use Simple to deserialize it to the following Java class:
#Root(name = "key", strict = false)
public class Key {
#Element(name = "element")
private String element;
// lots of more fields should be deserialized from xml
}
Note that the class does not have a field for the other element. I do not need the value of it in the class, but in an other place. How can i intercept the parsing and extract the value of this other element?
You can do it a number of ways, best is to use a Converter or a Strategy though. Converter is the easiest of the two.
I think the Strategy approach doesn't work, because they use the annotated class as the XML schema, and what is not present in the schema that will not be processed (visitors can't visit).
Converters can be used as follows:
#Root(name = "key", strict = false)
#Convert(KeyConverter.class)
public class Key {
private String element;
public Key(String elementValue) {
element = elementValue;
}
}
The converter stores the value during conversion:
public class KeyConverter implements Converter<Key> {
private String otherValue;
#Override
public Key read(InputNode node) throws Exception {
String elementValue = node.getNext("element").getValue().trim();
otherValue = node.getNext("other").getValue().trim();
return new Key(elementValue);
}
#Override
public void write(OutputNode arg0, Key arg1) throws Exception {
throw new UnsupportedOperationException();
}
/**
* #return the otherValue
*/
public String getOtherValue() {
return otherValue;
}
}
Putting together:
Registry registry = new Registry();
KeyConverter keyConverter = new KeyConverter();
registry.bind(Key.class, keyConverter);
Persister serializer = new Persister(new RegistryStrategy(registry));
Key key = serializer.read(Key.class, this.getClass().getResourceAsStream("key.xml"));
// Returns the value "acquired" during the last conversion
System.out.println(keyConverter.getOtherValue());
This is not too elegant, but might be suitable for your need.
I could not make a solution with a Stragegy or Converter as ng and Katona suggested. However, i made a workaround, which works, but not too nice.
/* package */ class SerializedKey extends Key {
#Element(name = "other", required = false)
private int mOtherValue;
public int getOtherValue() {
return mOtherValue;
}
}
...
Serializer serializer = new Persister();
SerializedKey key = serializer.read(SerializedKey.class, mInputStream);
int otherValue = key.getOtherValue();
Outside of the serialization package, i use Key as static type, so i simply forget that another field is in that object. When i persist my data, i also persist as a Key, so the mOtherValue is not connected to the class anymore. As you can see SerializedKey class is package-private, so i do not expose this helper class to any other component of my application.
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());