No converter found capable of converting from type Map<String, Object> to org.neo4j.driver.Value - cypher

I'm trying to update properties in a Node by passing HashMap<String, Object> as node properties in custom query using #Query in spring-data-neo4j
query reference from neo4j documentation
#Query("MATCH (m:Person {nid: $from})\n" +
"SET m += $props" +
"RETURN m")
Person updateInfo(#Param("from") String id, #Param("props") Map<String, Object> properties);
where it throws error on type conversion as below:
No converter found capable of converting from type
[xxxxx.service.controllers.ProfileController$1] to type
[org.neo4j.driver.Value]
Is mutating specific properties like in raw query possible in spring-data-neo4j? Please suggest a way to use property map.

Related

Why #JsonProperty not work for camel case properties in Kotlin

There is a very simple class:
class Price(
#JsonProperty("YPRICE")
val yprice: String? = null,
#JsonProperty("ZPRICE")
val zPrice: String? = null
)
And the following code to serialize to string:
val mapper = ObjectMapper().registerKotlinModule()
mapper.writeValue(System.out, Price())
The result in console is:
{"YPRICE":null,"zprice":null}
If changing the property of zPrice to zprice, then the result changes to:
{"YPRICE":null,"ZPRICE":null}
And if changing the property of yprice to yPrice, then the result changes to:
{"yprice":null,"zprice":null}
It seems that #JsonProperty does not work for the camel case properties.
You need to instruct ObjectMapper to generate JSON properties based on fields and not based on getter methods. You can use com.fasterxml.jackson.annotation.JsonAutoDetect annotation:
#JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.ANY)
class Price(
Since now, in all cases you should see the same result.
Take a look at:
Jackson/Hibernate, meta get methods and serialization
How to ignore "Is' methods with Jackson 2.2.3
InvalidDefinitionException: No serializer found for inner class

Using a Jackson attribute to accumulate state as a byproduct of serialization

Here's my scenario:
I have a deep compositional tree of POJOs from various classes. I need to write a utility that can dynamically process this tree without having a baked in understanding of the class/composition structure
Some properties in my POJOs are annotated with a custom annotation #PIIData("phone-number") that declares that the property may contain PII, and optionally what kind of PII (e.g. phone number)
As a byproduct of serializing the root object, I'd like to accumulate a registry of PII locations based on their JSON path
Desired data structure:
path
type
household.primaryEmail
email-address
household.members[0].cellNumber
phone-number
household.members[0].firstName
first-name
household.members[1].cellNumber
phone-number
I don't care about the specific pathing/location language used (JSON Pointer, Json Path).
I could achieve this with some reflection and maintenance of my own path, but it feels like something I should be able to do with Jackson since it's already doing the traversal. I'm pretty sure that using Jackson's attributes feature is the right way to attach my object that will accumulate the data structure. However, I can't figure out a way to get at the path at runtime. Here's my current Scala attempt (hackily?) built on top of a filter that is applied to all objects through a mixin:
object Test {
#JsonFilter("pii")
class PiiMixin {
}
class PiiAccumulator {
val state = mutable.ArrayBuffer[String]()
def accumulate(test: String): Unit = state += test
}
def main(args: Array[String]): Unit = {
val filter = new SimpleBeanPropertyFilter() {
override def serializeAsField(pojo: Any, jgen: JsonGenerator, provider: SerializerProvider, writer: PropertyWriter): Unit = {
if (writer.getAnnotation(classOf[PiiData]) != null) {
provider.getAttribute("pii-accumulator").asInstanceOf[PiiAccumulator].accumulate(writer.getFullName.toString)
}
super.serializeAsField(pojo, jgen, provider, writer)
}
override def include(writer: BeanPropertyWriter): Boolean = true
override def include(writer: PropertyWriter): Boolean = true
}
val provider = new SimpleFilterProvider().addFilter("pii", filter)
val mapper = new ObjectMapper()
mapper.addMixIn(classOf[Object], classOf[PiiMixin])
val accum = new PiiAccumulator()
mapper.writer(provider)
.withAttributes("pii-accumulator", accum)
.writeValueAsString(null) // Pass in any arbitrary object here
}
}
This code has enabled me to dynamically buffer up a list of property names that contain PII, but I can't figure out how to get their locations within the resulting JSON doc. Perhaps the Jackson architecture somehow precludes knowing that at runtime. Is there some other place I can hook in to do something like this, perhaps while converting to a JsonNode?
Thanks!
Okay, found it. You can access the recursive path/location during serialization via JsonGenerator.getOutputContext.pathAsPointer(). So by changing my code above to the following:
if (writer.getAnnotation(classOf[PIIData]) != null) {
provider.getAttribute("pii").asInstanceOf[PiiAccumulator]
.accumulate(jgen.getOutputContext.pathAsPointer().toString + "/" + writer.getName)
}
I'm able to dynamically buffer a list of special locations in the resulting JSON document for further dynamic processing.

Spring Data Solr: map type field

is it possible to define in #SolrDocument a field with type Map<String, List<String>>?
I've tried using:
#Indexed(name = "words", type = "string")
var words: Map<String, List<String>>?
I'm setting that field as
val words = mapOf(Pair("1111", listOf("word1", "word2"))) but when saving to Solr this field isn't saved at all. And when this document is found by SolrRepository the value for field words is null.
What type in #Indexed annotation do I have to use to get Map type?
If you are still looking for an answer:
#Field("words_*")
#Dynamic
var words: Map<String, List<String>>?
should do the trick

Issue when serializing Map.Entry using jackson

If I try to deserialize below type stored as String:
List<Entry<String, String>> entryList;
where entryList contains:
[{"dummyKey1":"dummyValue1"}]
I get the following errors
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.util.Map$Entry, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information.
I get above error while running a test case in junit, but if I remove the test case, then after deploying everything runs fine :
Above error comes while running junit test case because of absence of NoArgsConstructor in Entry. So, I created a DummyEntry with NoArgsConstructor that calls Entry with arguments as null.
DummyEntry<K, V> extends SimpleEntry<K, V>
After making this change, above error didn't come but I started getting below error after changes are deployed.
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "dummyKey1", not marked as ignorable (2 known properties: "value", "key"]).
What is the reason that one way doesn't works for junit, but in production it works while other does work in junit but not in production.
Also, I noticed one additional thing: In production, Map.Entry is serialized to
{'dummyKey1':'dummyValue1'}
whereas, test case in junit serializes the same string as
{'key':'dummyKey1', 'value':'dummyValue1'}
What is the reason about this weird behavior ? How can I make this thing work for both ?
I suspect you might be encountering an issue with different serialisation strategies for Map.Entry.
In v2.5.0 (IIRC) of jackson-databind Map.Entry was supported as a 'known type'. Prior to this version, the key and value attributes of Map.Entry would appear in a serialised Map.Entry. After this version, that's no longer the case.
Here are some example test cases showing what I mean:
#Test
public void mapSerialisationPreJackson2_5_0() throws IOException {
Map<String, String> aMap = Maps.newHashMap();
aMap.put("dummyKey1", "dummyValue1");
Set<Map.Entry<String, String>> incoming = aMap.entrySet();
ObjectMapper objectMapper = new ObjectMapper();
String serialised = objectMapper.writeValueAsString(incoming);
// prints: [{"key":"dummyKey1","value":"dummyValue1"}]
System.out.println(serialised);
Set<Map.Entry<String, String>> deserialised = objectMapper.readValue(serialised, Set.class);
// prints: [{key=dummyKey1, value=dummyValue1} (just like you posted in your question) whereas for versions > 2.5.0 the serialised form is ]
System.out.println(deserialised);
}
#Test
public void mapSerialisationPostJackson2_5_0() throws IOException {
Map<String, String> aMap = Maps.newHashMap();
aMap.put("dummyKey1", "dummyValue1");
Set<Map.Entry<String, String>> incoming = aMap.entrySet();
ObjectMapper objectMapper = new ObjectMapper();
String serialised = objectMapper.writeValueAsString(incoming);
// prints: [{"dummyKey1":"dummyValue1"}]
System.out.println(serialised);
Set<Map.Entry<String, String>> deserialised = objectMapper.readValue(serialised, Set.class);
// prints: [{dummyKey1=dummyValue1}]
System.out.println(deserialised);
}
Prior to v2.5.0 a Map.Entry would be serialised to {key=dummyKey1, value=dummyValue1} (just like you posted in your question) whereas for versions > 2.5.0 the serialised form is {dummyKey1=dummyValue1}.
I think you are using a version of jackson-databind in your test context which is < 2.5.0 and a version of jackson-databind in your production context which is > 2.5.0
In order to be able to deserialize [{"dummyKey1":"dummyValue1"}] into a List<Entry<String, String>> variable you can:
Use Jackson's parameter names module. Read more here. It basically allows non-annotated, non default constructors with parameters to be used for deserialization of a class. In this case the constructors of the various implementations of Map.Entry. A perfectly straightforward solution if you use Java 8 anyway.
If you can't use the parameter names module (e.g. Java 7), you can look into using mixins to annotate a constructor of a class without modifying it's source code. I had a go at that and it's tricky. For HashMap for instance the implementation of Map.Entry is Node which has package private visibility.

Inferring a generic type of Map in Kotlin

Consider a Java method which infers its type by Java class as follows:
public <T> T readJson(Class<T> c) throws IOException {
This allows doing something like this:
Map<String, String> map = foo.readJson(Map.class);
In java it will warn about unchecked cast, but it will work correctly. However in Kotlin, this will not be so easy, one could try using:
foo.readJson(Map::class.java)
However if Map<String, String> will be required, it will not work:
Type inference failed. Expected type mismatch.
required Map<String, String>
found Map<*, *>!
I also tried defining an interface StringMap:
interface StringMap : Map<String, String>
However that does not work either, it will lead to exceptions like this:
Cannot cast ...LinkedTreeMap to ...StringMap
What would be a correct way of doing this?
Kotlin does not have anything like Java raw types (which were left in Java for backward compatibility), and the type system therefore does not allow this kind of unchecked assignment to be made implicitly (star projections, the closest concept to raw types in Kotlin, retain type safety).
You can make an unchecked cast to Map<String, String>, thus expressing that you are aware of a possible type mismatch at runtime:
#Suppress("UNCHECKED_CAST")
val result = foo.readJson(Map::class.java) as Map<String, String>
You can suppress the unchecked cast warning for a broader scope than just one statement.
A natural improvement of this solution is writing a util function to hide the unchecked cast in it:
#Suppress("UNCHECKED_CAST")
inline fun <reified T: Any> JsonReader.readJson(): T {
val result = readJson(T::class.java)
return result as T
}
This solution uses an inline function with a reified type parameter: the function is transformed and substituted at each of its call sites, with T replaced by the specified (or inferred) type at compile time .
Usage examples:
val map = jsonReader.readJson<Map<String, String>>()
fun processMap(map: Map<String, String) { /* ... */ }
processMap(jsonReader.readJson()) // Map<String, String> is inferred for this call