Assume, I have the following class:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ObjectOfMonitoring {
private BigInteger id;
private Map<String, Object> properties = new HashMap<>();
#JsonAnySetter
public void add(String key, Object value) {
properties.put(key, value);
}
#JsonAnyGetter
public Map<String, Object> getProperties() {
return properties;
}
I test it in the following code:
ObjectOfMonitoring objectOfMonitoring = new ObjectOfMonitoring();
objectOfMonitoring.setId(BigInteger.ONE);
objectOfMonitoring.add("key1", "value1");
String jsonInString = mapper.writeValueAsString(objectOfMonitoring);
System.out.println(jsonInString);
I want to get result:
{"id":1,"key2":"value2","key1":"value1"}
But actual result is:
{"id":1,"properties":{"key2":"value2","key1":"value1"}}
What do I do incorrectly? And how to get the expected result?
Make sure that ObjectMapper and #JsonAnySetter/#JsonAnyGetter annotations are from the same packages.
All should be:
either from org.codehaus.jackson - which is the older
version of jackson
or from com.fasterxml.jackson - which
is newer (Jackson has moved from Codehaus when releasing Jackson 2 -
see here)
If you have both dependencies in your project and you are using them interchangeably you may have such hard to notice problems.
The best would be to just get rid of org.codehaus.jackson from your project dependencies.
Might be late to the party but thought of posting the answer as it can be helpful to someone in the future.
This is the sample code which you can configure accordingly for your class.
The main class which will read the JSON and associates with the class:
public class Main {
public static void main(String[] args) throws Exception {
File jsonFile = new File("test.json").getAbsoluteFile();
final ObjectMapper mapper = new ObjectMapper();
MyObject myObject = mapper.readValue(jsonFile, MyPojo.class);
}
}
Your custom POJO:
class MyObject {
private int id;
private Map<String,String> customField;
//Getter and Setter for POJO fields ommited
#JsonAnySetter
public void setCustomField(String key, Object value) {
System.out.println(" Key : " + key + " Value : " + value);
if(customField == null){
customField = new HashMap<String,Object>();
}
customField.put(key,value);
}
#JsonAnyGetter
public Map<String,String> getCustomField(){
return customField;
}
}
This will work if even for duplicate keys within the JSON if you are using the readValue from ObjectMapper but if you are using the treeToValue method from ObjectMapper then this would fail for the duplicate keys and you will have only the last value for the duplicated field. I am still figuring out a way to access the duplicate field while using the treeToValue method
Related
This is the class:
public class YamlMap {
Map<String, String> mp = new HashMap<>();
String get(String key) {
return this.mp.get(key);
}
}
and this is the props.yml:
mp:
key1: ok
key2: no
When I run:
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
mapper.findAndRegisterModules();
YamlMap ym2 = mapper.readValue(new File("src/main/resources/props.yml"), YamlMap.class);
then I get error:
Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "YamlMap" (class YamlMap)
A quick solution is to add #JsonProperty("mp") above your field :
public class YamlMap {
#JsonProperty("mp")
Map<String, String> mp;
}
Jackson core annotation names might be misleading but even though this annotation has Json in its' name - it will work. Jackson can be configured to parse different formats like Yaml or CBOR - but still for mapping you would use core annotations which have Json in their names.
Another solution is to create a constructor and use #JsonCreator :
public class YamlMap {
Map<String, String> mp;
#JsonCreator
public YamlMap(#JsonProperty("mp") Map<String, String> mp) {
this.mp = mp;
}
}
I use external application which expects an Object that Serializable from me like his function:
externalFunction(Object input);
So I should give that function an input that will be correctly serialized into JSON when the method is invoked (not controlled by me).
But I don't know how data is structured since I receive input from another external application dynamically. So case like this:
1. Get data from 3rd party
2. MyApp should annotate data for Json Serialization
3. Send data to 3rd party as input
4. Response will be produced as JSON
How can I achieve this? How can I give input to the function that is correctly serialized when the function is invoked?
What I tried so far:
So first thing I try is wrap data with some Wrapper like:
public class JsonWrapper<T> implements Serializable
{
public T attributes;
public JsonWrapper( T attributes )
{
this.attributes = attributes;
}
#JsonValue
public T getAttributes( )
{
return attributes;
}
}
So I wrap data like ->
data = getFromThirdParty();
wrapped = new JsonWrapper<>(data);
externalFunction(wrapped);
But it produces a response with "attributes" field which I don't want. Also I tried to use #JsonUnwrapped public T attributes; but the result is same.
I don't want this:
{
"attributes": {
... some fields/values that I don't know, get from 3rd party
}
}
I want like this:
{
... some fields/values that I don't know, get from 3rd party
}
The #JsonUnwrapped annotation doesn't work when T is a Collection (see this answer from the Jackson's creator). But the #JsonValue annotation actually does the trick:
public class JsonWrapper<T> {
#JsonValue
private T value;
public JsonWrapper(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
If you use Lombok, you can have:
#Getter
#AllArgsConstructor
public class JsonWrapper<T> {
#JsonValue
private T value;
}
Example
Consider the following class:
#Data
#AllArgsConstructor
public class Person {
private String firstName;
private String lastName;
}
When serializing an Person instance, the following result JSON is produced:
ObjectMapper mapper = new ObjectMapper();
JsonWrapper<?> wrapper = new JsonWrapper<>(new Person("John", "Doe"));
String json = mapper.writeValueAsString(wrapper);
{"firstName":"John","lastName":"Doe"}
When serializing a list of Person instances, the following result JSON is produced:
ObjectMapper mapper = new ObjectMapper();
JsonWrapper<?> wrapper = new JsonWrapper<>(
Arrays.asList(
new Person("John", "Doe"),
new Person("Jane", "Poe")
));
String json = mapper.writeValueAsString(wrapper);
[{"firstName":"John","lastName":"Doe"},{"firstName":"Jane","lastName":"Poe"}]
I am mapping the linkedHashMap to my Custom Pojo class using the below code.
ObjectMapper mapper = new ObjectMapper();**mapper.registerModule(new ParameterNamesModule()).registerModule(new Jdk8Module()).registerModule(new JavaTimeModule());** mapper.findAndRegisterModules(); mapper.convertValue(wrapper.getObject(), wrapper.getClassType());
This is giving me the below exception
"com.fasterxml.jackson.databind.JsonMappingException: Expected type float, integer, or string."
Previously, It was giving me a different exception(com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.time.Instant: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)) and after adding the highlighted code to the mapper then it started giving this exception. Could anyone help me figure out how to solve this exception?
I have the following test which works for me on Jackson 2.8.9.
public class FooTest {
public static class CustomBean implements Serializable {
private static final long serialVersionUID = 1L;
#JsonInclude(JsonInclude.Include.NON_DEFAULT)
public Instant time;
public String recordName;
public CustomBean() {
}
#Override
public String toString() {
return String.format("name:%s time:%s", recordName, time);
}
}
#Test
public void test_me() throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.findAndRegisterModules();
Map<String, Object> data = new LinkedHashMap<>();
data.put("recordName", "test");
data.put("time", Instant.now());
String json = mapper.writeValueAsString(data);
System.out.println(json);
CustomBean bean = mapper.convertValue(data, CustomBean.class);
System.out.println(bean);
}
}
The output I get is:
{"recordName":"test","time":1536738977.085000000}
name:test time:2018-09-12T07:56:17.085Z
Comparing this to your JSON output in the comment, it feels like your Instant is not being serialised correctly. Even though you are loading the JavaTimeModule so I don't really know why that is happening.
I need to write a JSON string that follows this basic format:
"gesamtAngebot":{
"angebotList":[{
"instanzId":"string",
"buchungsKontextList":[{
"quellSystem":"SOMETHING",
"payload":{}
}],
"payload":{"test1":"test1"}
}]
}
I'm using the following class to present the data, and an instance of this class is serialized with the Jackson ObjectMapper.
#Data
public class Angebot {
private String instanzId;
private List<BuchungsKontext> buchungsKontextList;
private Map<String, Object> payload = new HashMap<String, Object>();
#JsonAnyGetter
public Map<String, Object> any() {
return payload;
}
#JsonAnySetter
public void set(String name, Object value) {
payload.put(name, value);
}
}
If I serialize an instance of this class as-is, the resulting JSON will be something like this:
"gesamtAngebot":{
"angebotList":[{
"instanzId":"string",
"buchungsKontextList":[{
"quellSystem":"SOMETHING",
"payload":{}
}],
"payload":{"test1":"test1"},
"test1":"test1"
}]
}
As you can see the data of "payload" is doubled as it's own element and I don't have any idea why.
Thanks in advance for your attention and advice.
It seems like you want to serialize payload as a normal map. So if you don't want it there twice then you should not have the any() method, just have a regular getter method for payload.
The any() method can be used in case you want to serialize all the items from the payload map to appear like they are properties of Angebot class. Then you would use the any method, and not have a getter for payload.
Your JSON would come out like this:
"gesamtAngebot":{
"angebotList":[{
"instanzId":"string",
"buchungsKontextList":[{
"quellSystem":"SOMETHING",
"payload":{}
}],
"test1":"test1"
}]
}
And it will look like test1 is a variable of the Angebot class.
It is because of any() getter. Just remove it:
#Data
public class Angebot {
private String instanzId;
private List<BuchungsKontext> buchungsKontextList;
private Map<String, Object> payload = new HashMap<String, Object>();
// #JsonAnyGetter
// public Map<String, Object> any() {
// return payload;
// }
#JsonAnySetter
public void set(String name, Object value) {
payload.put(name, value);
}
}
payload is class property. It gets naturally de-serialized because of #Data annotation. any() getter creates duplicity.
I need to write a JSON string that follows this basic format:
{
"xmlns": {
"nskey1" : "nsurl1",
"nskey2" : "nsurl2"
},
"datakey1": "datavalue1",
"datakey2": "datavalue2"
}
I'm using the following class to present the data, and an instance of this class is serialized with the Jackson ObjectMapper.
public class PayloadData {
public Map<String, String> payloadData = new TreeMap<String, String>();
#JsonProperty("xmlns")
public Map<NamespaceEnum, String> namespaces = new TreeMap<NamespaceEnum, String>();
#JsonAnyGetter
public Map<String, String> getPayloadData() {
return payloadData;
}
}
If I serialize an instance of this class as-is, the resulting JSON will be something like this:
{
"xmlns": {
"nskey1" : "nsurl1",
"nskey2" : "nsurl2"
},
"payloadData": {
"datakey1": "datavalue1",
"datakey2": "datavalue2"
},
"datakey1": "datavalue1",
"datakey2": "datavalue2"
}
That makes sense based on the naming conventions, but I'm looking for a method to have the payloadData map placed in the JSON's root context without the duplicate that contains the property identifier and nesting. I've tried a lot of annotations in various forms; I've tried disabling the ObjectMapper wrap_root_value SerializationFeature; I honestly feel like I've tried just about everything. So before I throw a computer out the window, I'm asking for a second (and beyond) set of eyes to help point out what must be painfully obvious.
Thanks in advance for your attention and advice.
edit: updated the actual output JSON I see now. The data is being duplicated, and I'd like to remove the duplicate that has the nested property.
The problem is that you have 2 accessors exposed for PayloadData: the public property and the getter, so it is being serialized twice. If it is possible, I would recommend restructuring your data class to be immutable. For example:
public class PayloadData {
private final Map<String, String> payloadData;
private final Map<NamespaceEnum, String> namespaces;
#JsonCreator
public PayloadData(#JsonProperty("xmlns") Map<NamespaceEnum, String> namespaces,
#JsonProperty("payloadData") Map<String, String> payloadData) {
this.namespaces = namespaces;
this.payloadData = payloadData;
}
#JsonAnyGetter
public Map<String, String> getPayloadData() {
return payloadData;
}
#JsonProperty("xmlns")
public Map<NamespaceEnum, String> getNamespaces() {
return namespaces;
}
}
This will give you the desired output with out any configuration of the ObjectMapper.
You can parse your json data to a HashMap not a class object:
public HashMap<String, Object> testJackson(String data) throws IOException {
JsonFactory factory = new JsonFactory();
ObjectMapper mapper = new ObjectMapper(factory);
TypeReference<HashMap<String,Object>> typeRef
= new TypeReference<HashMap<String,Object>>() {};
HashMap<String,Object> o = mapper.readValue(data, typeRef);
return o
}
Get JSON data from a HashMap:
public String getJsonFromMap(HashMap<String, Object> data) {
return new ObjectMapper().writeValueAsString(data);
}