I have a class A (listed below) which can have many different types of attributes saved to it. Naturally these attributes can be of different types. Based on the type of attribute I want to apply some validations to it.
What would be the best way of doing it in Micronaut?
Here is an example of what I want to achieve:
public class A {
private String type;
private String value;
// getter/setter omitted…
}
Some example instances of class A:
{type: "type1", value: "examplevalue1"}
{type: "type2", value: "examplevalue2"}
{type: "type2", value: "examplevalue3"}
Then I have some set of validation rules which are relevant to the respective types. Each type (type1, type2, type3) have separate set of validation rules. These rules are not just restricted to String validation but also semantic and business validation.
I would solve this using specific class per type. You can introduce an interface A
interface A {
String getType();
}
and then implement the concrete types.
public class Type1 implements A {
#NotBlank
private String value;
#Override
public String getValue() { return this.value; }
public void setValue(String v) { this.value = v; }
}
public class Type2 implements A {
#YourCustomValidator
private String value;
#Override
public String getValue() { return this.value; }
public void setValue(String v) { this.value = v; }
}
and then implement a custom Jackson Deserializer which is able to build an instance of A by inspecting the JSON string field type.
I don't think that Drools has anything to do with this question.
Related
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 Value class which holds a value:
public class Value {
protected final Object value;
#JsonValue
public Object getValue() {
return value;
}
#JsonCreator
public Value(final Object value) {
this.value = value;
}
}
This Value class is embedded as a field (amongst other fields) in a data class:
class Data {
protected final Value value;
#JsonProperty("value")
public Value getValue() {
return value;
}
...
#JsonCreator
public Data(#JsonProperty("value") final Value value, ...) {
this.value = value;
....
}
}
When the input JSON has null for the value field of a data object (see below for example), Data.value is null. I would like to have Data.value set to new Value(null). In other words, the data object must hold a non-null value object, which holds the null.
{
"value" : null,
...
}
What is the easiest way to achieve this? I could ofcourse alter the constructor of Data, but I am wondering if Jackson could resolve this automatically.
You can write a custom de-serializer and override the getNullValue() method according to your requirements e.g.
public final class InstantiateOnNullDeserializer
extends JsonNodeDeserializer
{
#Override
public JsonNode getNullValue()
{
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.convertValue(new Value(null), JsonNode.class);
return node;
}
}
and register it on the value field of your Data class
class Data {
#JsonDeserialize(using = InstantiateOnNullDeserializer.class)
protected final Value value;
#JsonProperty("value")
public Value getValue() {
return value;
}
#JsonCreator
public Data(Value value) {
this.value = value;
}
}
Note that you need to remove the #JsonProperty("value") to avoid argument type mismatch. By removing JsonProperty annotation you create a so-called "delegate creator",
Jackson will than first bind JSON into type of the argument, and then call a creator
I do not believe that this is possible without creating your own deserializer or modify the constructor (or #JsonCreator).
From a good old thread in the Jackson User Group:
#JsonProperty does not support transformations, since the data binding is based on incremental parsing and does not have access to full tree representation.
So, in order to avoid a custom deserializer I would do something along the lines of:
#JsonCreator
public Data(#JsonProperty("value") final String value) {
this.value = new Value(value);
}
Which is kind of the opposite of what you asked ;)
Is there a way to bind an Enum attribute and not enum constant with Jpa column.
I have a enum like this:
public enum Type{
TYPE1("type1"),
TYPE2("type2);
private String enumValue;
Type(String enumValue){
this.enumValue = enumValue;
}
public String getValue(){
return enumValue;
}
}
And entity like this:
public class TestEntity{
#Enumerated(EnumType.STRING)
private Type type;
}
My entity always bind with enum constant i.e. TYPE1 and not with enumValue i.e. "type1". I could not find a way to do that. I am wondering is that is even possible with JPA.
Thanks,
You can do that with a hibernate-proprietary custom type (if you use Hibernate) or, if you use a JPA 2.1 implementation, with the new Convert annotation. Here's an article showing an example
#Convert(converter = TypeConverter.class)
private Type type;
...
#Converter
public class TypeConverter implements AttributeConverter<Type, String> {
#Override
public String convertToDatabaseColumn(Type attribute) {
return attribute.getValue();
}
#Override
public Type convertToEntityAttribute(String dbData) {
return Type.fromValue(dbData);
}
}
I have a non-static data which I need to use on conversion. How can I transfer this data into my adapter class? Probably can I use a XmlAdapter in JAXB RI without an empty constructor (and without annotation of course)?
public class VariableAdapter extends XmlAdapter<String, Variable> {
private Map<String, Variable> varMap;
public VariableAdapter(Map<String, Variable> aVarMap) {
varMap = aVarMap;
}
public Variable unmarshal(String aVarName) {
return varMap.get(aVarName);
}
public String marshal(Variable v) {
return v.getName();
}
}
Here is my class, which I need to convert from/into XML
public class Variable {
private String name;
private Object value;
public Value(String aName, Object aValue) {
name = aName;
value = aValue;
}
public String getName() {return name;}
public Object getValue() {return value;}
public void setValue(Object aValue) {value = aValue;}
}
All Variable objects are initialized before XML processing and must be serialized per its name. Variable after unmarshalling can get another value (if its value was changed between serialization/deserialization).
By default JAXB will create a new instance of the XmlAdapter. You can call the setAdapter method on Marshaller/Unmarshaller to specify a stateful one.
For More Information
http://blog.bdoughan.com/2011/09/mixing-nesting-and-references-with.html
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());