Custom Marshalling of HashMap<String,Object> in JAXB - java

I have a small problem regarding the marshalling via JAXB.
Currently I have a HashMap of Objects
#XmlJavaTypeAdapter(HashMapAdapter.class)
private Map<String, Object> data;
beeing marshalled by the Custom HashMapAdapter
public class HashMapAdapter extends XmlAdapter<HashMapAdapter.AdaptedMap,
Map<String, Object>> {
#XmlRootElement
public static class AdaptedMap {
#XmlVariableNode("key")
List<Data> entries = new ArrayList<>();
}
public static class Data {
#XmlTransient
public String key;
#XmlValue
public Object value;
}
#Override
public Map<String, Object> unmarshal(AdaptedMap v) throws Exception {
throw new NotImplementedException();
}
#Override
public AdaptedMap marshal(Map<String, Object> map) throws Exception {
AdaptedMap adaptedMap = new AdaptedMap();
for (Map.Entry<String, Object> entry : map.entrySet()) {
Data data = new Data();
data.key = entry.getKey();
data.value = entry.getValue();
adaptedMap.entries.add(data);
}
return adaptedMap;
}
}
The Marshalling is based on the following Post: http://blog.bdoughan.com/2013/06/moxys-xmlvariablenode-using-maps-key-as.html
The HashMap is filled with either Boolean, Long or String Values.
So regarding the Blog the expected JSON Output should be:
"data": {
"booleanValue": true,
"stringValue": "test",
"longValue": 1234
}
But the real outcome is:
"data": {
"longValue": {
"type": "long",
"value": 1234
},
"stringValue": {
"type": "string",
"value": "test"
},
"booleanValue": {
"type": "boolean",
"value": true
}
}
Im running on Payara Micro 174 and therefore on MOXy as JAXB provider.
Is it possible to get rid of the "type"/"value" nesting?
Best Regards
Simon

I do not have an MOxY implementation handy, could you try this and tell me if it works ?
public static class Data {
#XmlTransient
public String key;
#XmlElements({
#XmlElement(type=Long.class),
#XmlElement(type=String.class),
#XmlElement(type=Boolean.class)
})
#XmlPath(".")
public Object value;
}
EDIT :
The Output you get when using this Approach is:
"data": {
"stringValue": {
"value": test
},
"booleanValue": {
"value": true
},
"longValue": {
"value": 1234
}
}
Sadly this differs a little from the expected.

Related

How to deserialize a JSON file ( using Google JSON) consisting of same key name but uses different type?

Consider the following JSON File:
{
"version": "1.0",
"firstData": {
"meta": "this is string",
"version": "1"
},
"SecondData": {
"meta": ["string1", "string2", "string3"],
"version": "1"
},
"ThirdData": {
"meta": true,
"version": "1"
},
"FourthData": {
"meta": [true, false, false, true],
"version": "1"
},
"FifthData": {
"meta": [{
"meta": "string",
"version": "2"
},
{
"meta": ["string1","string2"],
"version": "2"
}]
"version": "1"
}
}
As seen, The "meta" attribute has different data type, sometimes it is String, sometimes it is ArrayOfString, sometimes Boolean etc.
Since my JSON file has several data,
I want it to follow the following Structure :
class information
{
String version;
HashMap<String,Data> details;
}
class Data
{
variable meta;
String version;
}
How do I create a corresponding POJO and deserialize it using Google GSON?
Just define your meta as JsonElement. Then you will have sort methods like: getAsString, getAsBoolean, getAsJsonObject, getAsJsonArray, ..., and also you are able to deserialize it again after you find out what is the type.
So your class could look like:
public class SomeClass {
private int version;
private JsonElement meta;
//getters and setters and other stuff
}
Edit: More elaboration and implementation
Define two classes: GeneralItem and GeneralData
class GeneralItem
{
public final int version;
public final JsonElement meta;
}
class GeneralData
{
public final String version;
public final Map<String, GeneralItem> items;
public GeneralData(String version, Map<String, GeneralItem> items)
{
this.version = version;
this.items = items;
}
}
And then we define a custom deserializer for our GeneralData:
class GeneralDataDeserializer implements JsonDeserializer<GeneralData>
{
#Override
public GeneralData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
final JsonObject object = json.getAsJsonObject();
final String version = object.get("version").getAsString();
object.remove("version");
HashMap<String, GeneralItem> items = new HashMap<>(object.size());
for (Map.Entry<String, JsonElement> item : object.entrySet())
items.put(item.getKey(), context.deserialize(item.getValue(), GeneralItem.class));
return new GeneralData(version, items);
}
}
Finally registering the deserializer to our gson instance and getting the data:
final Gson gson = new GsonBuilder()
.registerTypeAdapter(GeneralData.class, new GeneralDataDeserializer())
.create();
final String json = "your json here";
final GeneralData data = gson.fromJson(json, GeneralData.class);
System.out.println(data.items.get("firstData").meta.getAsString());
//other parts you want
(Note that constructors, getter and setters, error checking, etc. are removed for the sake of brevity)

JSON to Object Mapping

"statValues": {
"c__TL_gattooi": {
"value": 90.0
},
"c_cwc_gattooi": {
"value": 3462.0
},
"c_gaw__oxcgattooi": {
"value": 11.0
},
"c_odesb__ox_gattooi": {
"value": 6.0
},
"c_odesb_cwdc_gattooi": {
"value": 205472.0
},
"c_ach38_sax_gattooi": {
"value": 1.0
},
}
Want to convert this JSON to a POJO to be mapped with jackson
To deserialize given json from string jsonSource use something like
ObjectMapper mapper = new ObjectMapper();
Root root = mapper.readValue(jsonSource, Root.class);
I generated POJOs for you
import java.util.HashMap;
class Root {
private HashMap<String, Value> statValues = new HashMap<>();
public HashMap<String, Value> getStatValues() {
return statValues;
}
public void setStatValues(HashMap<String, Value> statValues) {
this.statValues = statValues;
}
}
class Value {
double value;
public double getValue() {
return value;
}
public void setValue(double value) {
this.value = value;
}
}
If can give better names it's possible by remapping properties using #JsonProperty
To access value use something like root.get("c__TL_gattooi").getValue(), to get list of keys - root.keys()

json to java hashmap using complex key

I am currently working on an app in which I need to serialize a
HashMap<Object1, Object2> into JSON and then deserialize from JSON to the same `HashMap'.
I am able to serialize it using the usual mapper and overriding the toString() method for Object1.
public String toString(){
String res = Object1.elem1 + ";" + Object1.elem2;
return res
}
I am then able to serialize and get the expected json (where res is the String I defined before easier not to write it all back).*
{res : Object2JsonRepresentation}
Then I want to deserialize, so I use a custom keyDeserializer :
#XmlElement(name="myMap")
#JsonDeserialize(keyUsing = Object1KeyDeserializer.class)
public HashMap <Object1,Object2> myMap = new HashMap <>();
And the Object1KeyDeserializer:
public class Object1KeyDeserializer extends KeyDeserializer{
#Override
public Object1 deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String[] parts = key.split(";");
System.out.println(key);
Elem elem1 = new Elem(parts[1]);
Elem elem2 = new Elem(parts[2]);
Object1 obj = new Object1(elem1,elem2);
return obj;
}
}
Nonetheless, the keyDeserializer never seems to be called, can you explain me the reason. I'm quite new to JSON and would be glad if answers could be detailed.
Instead of using toString() you can create your own serialization format. If you have non primitive key in Map then you can serialize Map as
[
{
"key": <serialized key>,
"value: <serialized value>
},
....
]
In this case your Serializer and Deserializer will be following:
public class CustomSerializer extends StdSerializer<Map<Object1, Object2>> {
protected CustomSerializer() {
super(Map.class, true);
}
#Override
public void serialize(Map<Object1, Object2> map,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException{
jsonGenerator.writeStartArray();
for (Map.Entry<Object1,Object2> element: map.entrySet()) {
jsonGenerator.writeStartObject();
jsonGenerator.writeObjectField("key", element.getKey());
jsonGenerator.writeObjectField("value", element.getValue());
jsonGenerator.writeEndObject();
}
jsonGenerator.writeEndArray();
}
}
and
public class CustomDeserializer extends StdDeserializer<Map<Object1, Object2>> {
protected CustomDeserializer() {
super(Map.class);
}
#Override
public Map<Object1, Object2> deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException {
Map<Object1, Object2> result = new HashMap<>();
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
for (JsonNode element : node) {
result.put(
jsonParser.getCodec().treeToValue(element.get("key"), Object1.class),
jsonParser.getCodec().treeToValue(element.get("value"), Object2.class)
);
}
return result;
}
}
So you can create class with your field and another Map (for checking that maps with different types works as usual):
public class MapWrapper {
#JsonSerialize(using = CustomSerializer.class)
#JsonDeserialize(using = CustomDeserializer.class)
private Map<Object1, Object2> map = new HashMap<>();
private Map<String, String> someMap = new HashMap<>();
// default constructor, getters, setters
}
Serialized value can be following:
{
"map": [
{
"key": {
"elem1": "qqq",
"elem2": "rrr"
},
"value": {
"fieldFromValue": "xxx"
}
},
{
"key": {
"elem1": "qqq_two",
"elem2": "rrr_two"
},
"value": {
"fieldFromValue": "yyy"
}
}
],
"someMap": {
"key1": "value1"
}
}

Injecting json property based on condition using Jackson

I have a json format which I am converting into Java Object Model using Jackson API. I am using Jaxsonxml 2.1.5 parser. The json response is as shown below.
{
"response": {
"name": "states",
"total-records": "1",
"content": {
"data": {
"name": "OK",
"details": {
"id": "1234",
"name": "Oklahoma"
}
}
}
}
}
Now json response format has changed. If the total-records is 1 the details will be an object with id and name attributes. But if the total-records is more than 1 then the details will be an array of object like below:
{
"response": {
"name": "states",
"total-records": "4",
"content": {
"data": {
"name": "OK",
"details": [
{
"id": "1234",
"name": "Oklahoma"
},
{
"id": "1235",
"name": "Utah"
},
{
"id": "1236",
"name": "Texas"
},
{
"id": "1237",
"name": "Arizona"
}
]
}
}
}
}
My Java Mapper class looks like below with earlier json response.
#JsonIgnoreProperties(ignoreUnknown = true)
public class MapModelResponseList {
#JsonProperty("name")
private String name;
#JsonProperty("total-records")
private String records;
#JsonProperty(content")
private Model model;
public Model getModelResponse() {
return model;
}
public void setModel(Model model) {
this.model = model;
}
}
Client Code
package com.test.deserializer;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com..schema.model.Person;
public class TestClient {
public static void main(String[] args) {
String response1="{\"id\":1234,\"name\":\"Pradeep\"}";
TestClient client = new TestClient();
try {
Person response = client.readJSONResponse(response1, Person.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public <T extends Object> T readJSONResponse(String response, Class<T> type) {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
T result = null;
try {
result = mapper.readValue(response, type);
} catch (Exception e) {
e.printStackTrace();
}
return (T) result;
}
}
Now based on the total-records how to handle to mapping to either a Model or list of Model Object. Please let me know.
You need a custom deserializer. The idea is to mix and match object processing with tree processing. Parse objects where possible but use the tree (JSONNode) for custom handling.
On the MapModelResponseList, remove the records property and add a List<Data> array where Data is just a holder class for the id/name pairs. You can get the total records by returning the size of this list.
In the deserializer, do the following:
public final class MapModelDeserializer extends BeanDeserializer {
public MapModelDeserializer(BeanDeserializerBase src) {
super(src);
}
protected void handleUnknownProperty(JsonParser jp, DeserializationContext ctxt, Object beanOrClass, String propName) throws IOException, JsonProcessingException {
if ("content".equals(propName)) {
MapModelResponseList response = (MapModelResponseList) beanOrClass;
// this probably needs null checks!
JsonNode details = (JsonNode) jp.getCodec().readTree(jp).get("data").get("details");
// read as array and create a Data object for each element
if (details.isArray()) {
List<Data> data = new java.util.ArrayList<Data>(details.size());
for (int i = 0; i < details.size(); i++) {
Data d = jp.getCodec().treeToValue(details.get(i), Data.class);
data.add(d);
}
response.setData(data);
}
// read a single object
else {
Data d = jp.getCodec().treeToValue(details, Data.class);
response.setData(java.util.Collections.singletonList(d));
}
super.handleUnknownProperty(jp, ctxt, beanOrClass, propName);
}
Note that you do not implement deserialize() - the default implementation is used to create the MapModelResponseList as normal. handleUknownProperty() is used to deal with the content element. Other data you don't care about is ignored due to #JsonIgnoreProperties(ignoreUnknown = true) in the super call.
This is a late answer, but I solve it in a different way. It can work by catching it in Object like this:
#JsonProperty("details")
public void setDetails(Object details) {
if (details instanceof List) {
setDetails((List) details);
} else if (details instanceof Map) {
setDetails((Map) details);
}
}
public void setDetails(List details) {
// your list handler here
}
public void setDetails(Map details) {
// your map handler here
}

Jackson json arraylist of unknown fields

I have a JSON string like this:
{
"123": {
"hi": {
"name": "John",
"phone": "12345"
}
},
"124": {
"hi": {
"name": "James",
"phone": "12345"
}
},
"125": {
"hi": {
"name": "Leo",
"phone": "12347"
}
}
}
The JSON is ordered externally: 123, 124, 125 I just want to get the value of the last field ("125" : {...}) but I don't know its name, is auto-generated. What's the best way?
I'm trying mapping to an arrayList of JsonNodes, but I don't find the right way.
You can also do it using #JsonAnySetter annotation. Please, see below example. POJO class:
class Node {
private Integer key = Integer.MIN_VALUE;
private JsonNode value;
#JsonAnySetter
public void set(String newKey, JsonNode newValue) {
if (Integer.valueOf(newKey) > key) {
value = newValue;
}
}
public JsonNode getValue() {
return value;
}
#Override
public String toString() {
return String.valueOf(value);
}
}
Example usage:
ObjectMapper mapper = new ObjectMapper();
Node node = mapper.readValue(json), Node.class);
System.out.println(node);
Prints:
{"hi":{"name":"Leo","phone":"12347"}}
I did it putting the JSON string in a JsonNode, data and mapping to a NavigableMap.
NavigableMap<String, JsonNode> updates = mapper.readValue(data.traverse(), new TypeReference<TreeMap<String, JsonNode>>() {});
Entry<String, JsonNode> lastEntry = updates.lastEntry();
String lastUpdate = lastEntry.getValue().toString();

Categories

Resources