I have a Java project that currently returns a map to Camel without any nested key,value pairs, and the Jackson library marshalls it just fine into JSON using the Jackson library.
For example if I put the following two key,values into a demoMap:
Map<String,String> demoMap = new TreeMap<String,String>
demoMap.put("key1","5");
demoMap.put("key2","10");
I get the following JSON:
{"key1":"5","key2":"10"}
However, now some of my key,value entries will have an optional visibility that I need to put as a nested key value in my map. Any key,values that don't have an optional visibility will use the default. So, for example if I specify visibilities for key1, and key2, but not key3 I want to get JSON out that looks like this:
{"key1":"5",
"key2":"10",
"key3":"17",
"visibility" : { "key1": "a&b&!c", "key2": "a&b", "_default": "a" }
}
How can I get Camel to marshall a Java object with nested key,value pairs? I'm a visual learner, so a simple example would be helpful.
I tried changing my Map to have a value as an object i.e.,:
Map<String,Object> demoMap = new TreeMap<String,Object>
and then tried adding nested key,values for some keys with an ArrayList using http://examples.javacodegeeks.com/core-java/json/jackson/convert-java-map-to-from-json-using-jackson-example/ for reference, but realized that this just gives me a bunch of nested values under a key, not a bunch of nested key,value pairs.
Even when I tried it for grins, I got an error from the Camel processor with a java.lang.ClassCastException stating java.util.ArrayList cannot be cast to java.lang.String
And similarly when I tried to nest a Map inside my demoMap I got this ClassCastException:
3244 [hello.world.request.timer] ERROR org.apache.camel.processor.DefaultErrorHandler - Failed delivery for exchangeId: e6518e39-89b7-435e-96d9-ce26811ac67e. Exhausted after delivery attempt: 1 caught: java.lang.ClassCastException: java.util.HashMap cannot be cast to java.lang.String
So I know how NOT to do it. :-/
I re-read the Camel JSON documentation at http://camel.apache.org/json.html but as of this writing it doesn't specify an example with nested key,value pairs.
UPDATE: Based on feedback from Tom I created two Maps i.e.,
Map<String,String> keyvalues = new TreeMap<String,String>();
Map<String,String> visibility = new TreeMap<String,String>();
Here is my class which I call SensorGenerator that loads a properties file into a Map:
package sample;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import org.codehaus.jackson.annotate.JsonAnyGetter;
import org.codehaus.jackson.annotate.JsonProperty;
public class SensorGenerator {
private Properties sourceProperties;
// create a map of sensor keyvalues, and a map of sensor visibility
Map<String,String> keyvalues = new TreeMap<String,String>();
@JsonProperty
Map<String,String> visibility = new TreeMap<String,String>();
@JsonAnyGetter
public Map<String, String> getKeyvalues() {
for (Object key : sourceProperties.keySet()) {
// Separate out each of the field:datatype:visibility tuples as an entry in the
// values array
String[] values = sourceProperties.getProperty((String) key).split(
",");
// split the key between 'sensor' and the 'number' Ex: sensor1 -> sensor,1
String[] keyArray = key.toString().split("(?<=([a-zA-Z]++))");
String keyNumber = keyArray[1]; // grab the number to append for each sensor
// define string buffer that appends sensor number for each sensor's
// keys. Ex: sensor1 would have s1make, s1makeDataType, etc.
StringBuffer sensorNumberStringBuffer = new StringBuffer();
sensorNumberStringBuffer.append("s");
sensorNumberStringBuffer.append(keyNumber);
// make, its data type, and visibility (with s# prefix)
StringBuffer makeStringBuffer = new StringBuffer();
makeStringBuffer.append(sensorNumberStringBuffer);
makeStringBuffer.append("make");
StringBuffer makeDataTypeStringBuffer = new StringBuffer();
makeDataTypeStringBuffer.append(sensorNumberStringBuffer);
makeDataTypeStringBuffer.append("makeDataType");
StringBuffer makeVizStringBuffer = new StringBuffer();
makeVizStringBuffer.append(sensorNumberStringBuffer);
makeVizStringBuffer.append("makeViz");
// model, its data type, and visibility (with s# prefix)
StringBuffer modelStringBuffer = new StringBuffer();
modelStringBuffer.append(sensorNumberStringBuffer);
modelStringBuffer.append("model");
StringBuffer modelDataTypeStringBuffer = new StringBuffer();
modelDataTypeStringBuffer.append(sensorNumberStringBuffer);
modelDataTypeStringBuffer.append("modelDataType");
StringBuffer modelVizStringBuffer = new StringBuffer();
modelVizStringBuffer.append(sensorNumberStringBuffer);
modelVizStringBuffer.append("modelViz");
// serialNumber, its data type, and visibility (with s# prefix)
StringBuffer serialNumberStringBuffer = new StringBuffer();
serialNumberStringBuffer.append(sensorNumberStringBuffer);
serialNumberStringBuffer.append("serialNumber");
StringBuffer serialNumberDataTypeStringBuffer = new StringBuffer();
serialNumberDataTypeStringBuffer.append(sensorNumberStringBuffer);
serialNumberDataTypeStringBuffer.append("serialNumberDataType");
StringBuffer serialNumberVizStringBuffer = new StringBuffer();
serialNumberVizStringBuffer.append(sensorNumberStringBuffer);
serialNumberVizStringBuffer.append("serialNumberViz");
// sensorType, its data type, and visibility (with s# prefix)
StringBuffer sensorTypeStringBuffer = new StringBuffer();
sensorTypeStringBuffer.append(sensorNumberStringBuffer);
sensorTypeStringBuffer.append("sensorType");
StringBuffer sensorTypeDataTypeStringBuffer = new StringBuffer();
sensorTypeDataTypeStringBuffer.append(sensorNumberStringBuffer);
sensorTypeDataTypeStringBuffer.append("sensorTypeDataType");
StringBuffer sensorTypeVizStringBuffer = new StringBuffer();
sensorTypeVizStringBuffer.append(sensorNumberStringBuffer);
sensorTypeVizStringBuffer.append("sensorTypeViz");
// put all the field:datatype keyvalues for this sensor in the keyvalues map
// and visibilities in the visibility map
// make, data type, and visibility
keyvalues.put(makeStringBuffer.toString(), values[0].split(":")[0]);
keyvalues.put(makeDataTypeStringBuffer.toString(), values[0].split(":")[1]);
visibility.put(makeVizStringBuffer.toString(), values[0].split(":")[2]);
// model, data type, and visibility
keyvalues.put(modelStringBuffer.toString(), values[1].split(":")[0]);
keyvalues.put(modelDataTypeStringBuffer.toString(), values[1].split(":")[1]);
visibility.put(modelVizStringBuffer.toString(), values[1].split(":")[2]);
// serialNumber, data type, and visibility
keyvalues.put(serialNumberStringBuffer.toString(), values[2].split(":")[0]);
keyvalues.put(serialNumberDataTypeStringBuffer.toString(), values[2].split(":")[1]);
visibility.put(serialNumberVizStringBuffer.toString(), values[2].split(":")[2]);
// sensorType, data type, and visibility
keyvalues.put(sensorTypeStringBuffer.toString(), values[3].split(":")[0]);
keyvalues.put(sensorTypeDataTypeStringBuffer.toString(), values[3].split(":")[1]);
visibility.put(sensorTypeVizStringBuffer.toString(), values[3].split(":")[2]);
// add in default visibility
visibility.put("_default", "a");
}
return keyvalues;
}
public void setSourceProperties(Properties properties) {
this.sourceProperties = properties;
}
}
Right now I just hardcoded the default visibility to "a", but will change that later to also be pulled from a properties file.
Your structure is a more than a map. It's two maps that are serialised differently. One way to represent this is:
public class Whatever{
Map<String,String> keyvalues;
Map<String,String> visibility;
}
What you'll end up with is this, which although represents the data is far from ideal:
{
"keyvalues" : { "key1": "5", "key2": "10", "key3": "17"},
"visibility" : { "key1": "a&b&!c", "key2": "a&b", "_default": "a" }
}
To get what you want, use @JsonAnyGetter. Something like this (it could be made much easier to use):
public class Whatever{
Map<String,String> keyvalues = new TreeMap<String,String>();
@JsonProperty
Map<String,String> visibility = new TreeMap<String,String>();
@JsonAnyGetter
public Map<String, String> getKeyvalues() {
return keyvalues;
}
}
which produces:
{"visibility":{"key1":"a&b&!c","key2":"a&b"},"key1":"5","key2":"10"}
I've been battling this today and your question inspired me to make it bloody work :D The annotations are here: https://github.com/FasterXML/jackson-annotations/wiki/Jackson-Annotations
See JUnit test here: https://gist.github.com/TomDemeranville/7009250
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With