Johnzon sub dynamic json tree mapping - java

In Apache Johnzon, is there a way to hava a generic field that contains dynamic JSON data, not mappable to a pre-defined POJO?
In Jackson you can simply use ObjectNode as a generic container, have some JSON processing on it, and then write the whole object in JSON format.
In Jackson it works as expected using ObjectNode, here is my code:
public class JsonTest {
private String myStaticKey = "foo";
private ObjectNode jsonData;
//code to initialize ObjectNode + getters + setters
#JsonIgnore
public void addValue(String key, String value) {
jsonData.put(key, value);
}
#JsonIgnore
public String toJson() {
return new ObjectMapper().writeValueAsString(this);
}
}
public class MainTest {
public static void main(String[] args) {
JsonTest t = new JsonTest();
t.addValue("myDynamicKey", "bar");
System.out.println(t.toJson());
}
}
Expected result:
{
"myStaticKey": "foo",
"jsonData": {
"myDynamicKey": "bar"
}
}

Related

Jackson objectMapper mapping different json properties to same pojo

I have a very simple json which I am trying to map to an object.
JSON :
[
{
"cust_lpid": "0119b9f7f99ad2161de7b0b",
"cust_uid": "soumavtestflow"
}
]
My Mapper Class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class CustomerSegmentRequest {
#JsonProperty("LPID")
String cust_lpid;
#JsonProperty("UserId")
String cust_uid;
public String getCust_lpid() {
return cust_lpid;
}
public void setCust_lpid(String cust_lpid) {
this.cust_lpid = cust_lpid;
}
public String getCust_uid() {
return cust_uid;
}
public void setCust_uid(String cust_uid) {
this.cust_uid = cust_uid;
}
}
When I do a
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
LPIDCustIDMapper[] custSegResp = objectMapper.readValue(responseBody,CustomerSegmentRequest [].class);
I don't get any values populated in custSegResp.
However when i remove the #JsonProperty it works.
I need the json property name to map an incoming request and hence don't wanna create a separate mapping class.
Is there a way to achieve the same?
use #JsonAlias
#JsonIgnoreProperties(ignoreUnknown = true)
public class CustomerSegmentRequest {
#JsonAlias({"cust_lpid", "LPID" })
String cust_lpid;
#JsonAlias({"cust_uid", "UserId" })
String cust_uid;
public String getCust_lpid() {
return cust_lpid;
}
public void setCust_lpid(String cust_lpid) {
this.cust_lpid = cust_lpid;
}
public String getCust_uid() {
return cust_uid;
}
public void setCust_uid(String cust_uid) {
this.cust_uid = cust_uid;
}
}
#JsonProperty("keyName") is to specify what is the key is the JSON which maps to this field.
The reason it works without it is, without it Jackson tries to first match via getters/setters after removing the get/set prefix keys and normalizing the case (getAbCd -> abCd), which in your case gives the keys as in the JSON.
You need to modify your #JsonProperty("LPID") to #JsonProperty("cust_lpid") or if you need to map to multiple keys use #JsonAlias({"cust_lpid", "LPID" })

Gson nested objects

I'm using a library with a class to map a json message and I'm using Gson to serialized the classes over json. The message contains a data field. The field is generic and it contains anything. The class provided by the library is:
public class Api {
....
#SerializedName("data")
Map<String, JsonElement> data;
....
}
Now I'd like to extend the class but I have my own root object to map the data sub field, so to do a summary the json is something like:
{...., "data": {"myownroot":"aaaa"}}
How can I do? I could create my own classes of course but I would prefer to extend the library if possible. If I extend the class I would have:
public class MyOwnRoot extends Api {
#SerializedName("myownroot")
public String root;
}
but in this case when I serialize it doesn't work because myownroot must be a child of data but how can I say to Gson "put MyOwnRoot in the data map"??
I can't really understand why you would need to extend the Api class.
I guess the class name is a bit of a misnomer, since API are (generally speaking) interfaces, and they get implemented.
I guess you could do this by using getters to your advantages, while letting Gson do its work on fields.
EDIT: To serialize also
public class Api {
#SerializedName("data")
protected Map<String, JsonElement> data;
}
public class RootEntity extends Api {
transient StructuredRootImpl _cached;
transient Gson _gson = new Gson();
public class StructuredRootImpl {
Integer v;
String name;
}
public Integer getV() {
synch();
return _cached.v;
}
public void setV(Integer v) {
_cached.v=v;
synch();
}
private void synch() {
if(_cached==null){
if(data==null){
data = new LinkedHashMap<>();
}
JsonElement jsonElement = data.get("myroot");
_cached = _gson.fromJson(jsonElement, StructuredRootImpl.class);
}
JsonElement jsonTree = _gson.toJsonTree(_cached);
data.put("myroot", jsonTree);
}
public String getName() {
synch();
return _cached.name;
}
public void setName(String name) {
_cached.name = name;
synch();
}
#Override
public String toString() {
return "RootEntity [v=" + getV() + ", n=" + getName() + "]";
}
}
Running the main you can both serialize and deserialize your entity.
public class TestGson {
public static void main(String[] args) {
String jsonText = "{data:{\"myroot\":{\"v\":123,\"name\":\"mario\"}}}";
Gson gson = new Gson();
RootEntity entity = gson.fromJson(jsonText, RootEntity.class);
System.out.println(entity);
entity.setName("Alex");
entity.setV(150);
String thenBack = gson.toJson(entity);
System.out.println(thenBack);
}
}
This will result in :
so.alpha.TestGson$Foo#4b85612c
StructuredRoot [v=123, name=mario]
Still I don't understand why you would extend the Api class.

How to unit test enum composed via #JsonCreator?

I am using Jackson to deserialize a JSON string into an enum.
public enum RoomType {
SHARED("shared"),
PRIVATE("private");
private String value;
RoomType(String value) {
this.value = value;
}
#JsonCreator
public static RoomType fromJson(final String jsonValue) {
for (RoomType type : values()) {
if (type.value.equals(jsonValue)) {
return type;
}
}
return null;
}
#JsonValue
#Override
public String toString() {
return value;
}
}
I want to unit test the different edge cases:
#RunWith(JUnit4.class)
public class RoomTypeTest {
private final ObjectMapper mapper = new ObjectMapper();
#Test
public void fromJsonWithShared() throws Exception {
String json = "{\"roomType\":\"shared\"}";
RoomType type = mapper.readValue(json, RoomType.class);
assertThat(type).isEqualTo(RoomType.SHARED);
}
}
The test fails. When I debug I see that jsonValue is null when RoomType.fromJson is invoked. Seems like that Jackson does not pick up the value from the JSON string.
Related examples
EnumCreatorTest929.java
I think Jackson doesn't know what value to pass to that fromJson method. Try adding #JsonProperty:
#JsonCreator
public static RoomType fromJson(#JsonProperty("roomType") final String jsonValue) {
....
}

deserialize Json into POJO

I am trying to convert the following JSON structure (part of a larger JSON object) to a POJO but getting the exception copied below (using Java/Jackson).
JSON
"outputKeys":
{"ABC":"gGyIioUr4Jfr5QiCm6Z==",
"DEF":"RxHfNyD2JyPOpG5tv3Jaj5g=="}
Java class
private class OutputKeys {
private String key;
private String value;
public OutputKeys(String key, String value) {
this.key = key;
this.value = value;
}
}
&
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(jsonString, Test.class);
exception:
no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?
Test class has the OutputKeys as an attribute.
Any suggestions would be welcome. I have tried using a List of OutputKeys as well .
Update:
I have tried the following without success:
class OutputKeys {
public Map<String, String> keys;
///with constructor/setter/getters
}
&
class OutputKeys {
public List<OutputKey> keys;
///with constructor/setter/getters
public class OutputKey {
Map<String, String> outputs = new HashMap<>();
// tried this too:
// String key
//String value
}
You require below mentioned single class only, containing
All keys(ABC and DEF)
getters/setters
toString() which you'll use interact with JSON.
public class OutputKeys
{
private String ABC;
private String DEF;
public String getABC ()
{
return ABC;
}
public void setABC (String ABC)
{
this.ABC = ABC;
}
public String getDEF ()
{
return DEF;
}
public void setDEF (String DEF)
{
this.DEF = DEF;
}
#Override
public String toString()
{
return "ClassPojo [ABC = "+ABC+", DEF = "+DEF+"]";
}
}
Let me know if you require more details.
Since the keys were dynamic, I ended up deserializing the data using the iterator on the JsonNode:
jsonNode.get("outputKeys").iterator()
& then getting the relevant dynamic key information via the iterator.
I needed a similar tool for NodeJS. So that I can write tests on parts of a bigger model that was serialized (JSON).
So, if I need only "ABC":"gGyIioUr4Jfr5QiCm6Z==" or "XYZ":{"Hello": "My String", "Content": [1,2,3]}, the only property I care to test at the moment is:
var sutXYX = { Hello: "My String", Content: [ 1, 2, 2]};
I wrote this tool as a utility https://github.com/whindes/PojoScriptifyFromJSON

Json Deserialization in Java /w Jackson of mixed types, contained in one array

Consider the following json, getting from an public API:
anyObject : {
attributes: [
{
"name":"anyName",
"value":"anyValue"
},
{
"name":"anyName",
"value":
{
"key":"anyKey",
"label":"anyLabel"
}
}
]
}
As you can see, sometimes the value is a simple string and sometimes its an object. Is it somehow possible to deserialize those kind of json-results, to something like:
class AnyObject {
List<Attribute> attributes;
}
class Attribute {
private String key;
private String label;
}
How would I design my model to cover both cases. Is that possible ?
Despite being hard to manage as others have pointed out, you can do what you want. Add a custom deserializer to handle this situation. I rewrote your beans because I felt your Attribute class was a bit misleading. The AttributeEntry class in the object that is an entry in that "attributes" list. The ValueObject is the class that represents that "key"/"label" object. Those beans are below, but here's the custom deserializer. The idea is to check the type in the JSON, and instantiate the appropriate AttributeEntry based on its "value" type.
public class AttributeDeserializer extends JsonDeserializer<AttributeEntry> {
#Override
public AttributeEntry deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode root = p.readValueAsTree();
String name = root.get("name").asText();
if (root.get("value").isObject()) {
// use your object mapper here, this is just an example
ValueObject attribute = new ObjectMapper().readValue(root.get("value").asText(), ValueObject.class);
return new AttributeEntry(name, attribute);
} else if (root.get("value").isTextual()) {
String stringValue = root.get("value").asText();
return new AttributeEntry(name, stringValue);
} else {
return null; // or whatever
}
}
}
Because of this ambiguous type inconvenience, you will have to do some type checking throughout your code base.
You can then add this custom deserializer to your object mapper like so:
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(AttributeEntry.class, new AttributeDeserializer());
objectMapper.registerModule(simpleModule);
Here's the AttributeEntry:
public class AttributeEntry {
private String name;
private Object value;
public AttributeEntry(String name, String value) {
this.name = name;
this.value = value;
}
public AttributeEntry(String name, ValueObject attributes) {
this.name = name;
this.value = attributes;
}
/* getters/setters */
}
Here's the ValueObject:
public class ValueObject {
private String key;
private String label;
/* getters/setters */
}

Categories

Resources