Is it possible to get JsonRootName of nested child with Jackson? - java

I have nested objects that I want to get the root name from when I serialize it to json using jackson. this is the result I want(I apologize to all the flower enthusiasts but just needed a example, this is not to be interpreted as accurate data):
{
"plants": "flowers",
"types": {
"rose" : {
"color": "red",
"height": 25,
}
}
}
I got 2 classes, one wrapper class:
public class JsonWrapper {
public String plants = "flowers";
public Object types;
}
and the "flower" class:
#JsonRootName("rose")
public class rose{
public String color = "red";
public int height = 25;
}
now what I do is this:
JsonWrapper wrapper = new JsonWrapper();
wrapper.types = new rose();
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
String jsonInString = mapper.writeValueAsString(wrapper);
The result i get is:
{
"JsonWrapper":{
"plants": "flowers",
"types": {
"color": "red",
"height": 25,
}
}
}
So i get the root name for the wrapper object that is not annotated but not for the child object that is annotated. is there a way to tell jackson not to get a root name where there is none and also analyze child objects?

An alternate solution without JsonRootName.
JsonWrapper.java
import java.util.HashMap;
import java.util.Map;
public class JsonWrapper {
public String plants = "flowers";
Map<String, Flower> types = new HashMap<>();
public Map<String, Flower> getTypes() {
return types;
}
public void setTypes(Map<String, Flower> types) {
this.types = types;
}
}
Flower.java
public class Flower {
public String color = "red";
public int height = 25;
}
Main.java
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) throws JsonProcessingException {
JsonWrapper wrapper = new JsonWrapper();
wrapper.getTypes().put("rose", new Flower());
ObjectMapper mapper = new ObjectMapper();
String jsonInString = mapper.writeValueAsString(wrapper);
System.out.println(jsonInString);
}
}

Related

How to serialize json to Map?

I need to serialize json to Map. My son looks like the following:
{
items: [{
"name": "Test1",
"value": {
"id": 1,
"count": 5
}
}]
}
and I have following classes:
public class Value {
public int id;
public int count;
}
public class ItemManager {
public Map<String, Value> items;
}
and I was trying to deserialize it like that:
class Main {
public static void main(String... args) {
ObjectMapper mapper = new ObjectMapper();
ItemManager manager = mapper.read(args[0], ItemManager.class);
}
}
but I get the following exception:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `test.ItemManager` out of START_ARRAY token
at [Source: (String)"{
I need to put name as a key and value as a value.
Can anyone help to do it?
Here is a simple custom Deserializer to do what you want (I’ve used Jackson v2.12.4) :
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
public class ItemManagerDeserializer extends StdDeserializer<ItemManager> {
public ItemManagerDeserializer() {
this(null);
}
public ItemManagerDeserializer(Class<?> vc) {
super(vc);
}
#Override
public ItemManager deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
ObjectCodec mapper = jp.getCodec();
ItemManager itemManager = new ItemManager();
for (JsonNode element : node.get("items")) {
String key = element.get("name").asText();
ItemManager.Value value = mapper.treeToValue(element.get("value"), ItemManager.Value.class);
itemManager.getItems().put(key, value);
}
return itemManager;
}
}
I’ve created a static Value class in the ItemManager class and instantiated the items property in the constructor (new HashMap<>()).
Then, you can register and use this Deserializer like so :
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(ItemManager.class, new ItemManagerDeserializer());
mapper.registerModule(module);
ItemManager itemManager = mapper.readValue(args[0], ItemManager.class);
itemManager.getItems().forEach((key, value) -> System.out.println("key: " + key + ", value: " + value));
}

Different fields with same JsonProperty attribute

Is it possible to have something like below while serializing a JSON in the same class
#JsonProperty("stats")
private StatsDetails statsDetails
#JsonProperty("stats")
private List<StatsDetails> statsDetailsList
so i can have either statsDetails or statsDetailsList only one of these being included while forming a json.
I also have a separate JsonMapper code that transforms this pojo data into a json which i haven't included here.
You cannot do that. It will throw JsonMappingException jackson cannot know which of the fields are you referring to. You can try it by yourself with the following code:
POJOClass:
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import java.util.List;
public class POJOClass {
public POJOClass(String object) {
this.object = object;
}
public POJOClass(List<String> objectList) {
this.objectList = objectList;
}
#JsonProperty("object")
public String object;
#JsonProperty("object")
public List<String> objectList;
#JsonGetter("object")
public String getObject() {
return object;
}
#JsonGetter("object")
public List<String> getObjectList() {
return objectList;
}
#JsonSetter("object")
public void setObject(String object) {
this.object = object;
}
#JsonSetter("object")
public void setObjectList(List<String> objectList) {
this.objectList = objectList;
}
}
Main class:
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
public class MainClass {
public static void main(String[] args) {
String text = "f";
List<String> list = Arrays.asList("a", "b", "c");
ObjectMapper mapper = new ObjectMapper();
try {
String json = mapper.writeValueAsString(new POJOClass(text));
String listJson = mapper.writeValueAsString(new POJOClass(list));
System.out.println("json=" + json);
System.out.println("listJson=" + listJson);
} catch (IOException e) {
e.printStackTrace();
}
}
}
The output:
com.fasterxml.jackson.databind.JsonMappingException: Multiple fields representing property "object": POJOClass#object vs POJOClass#objectList

Using jackson deserialising a property which can be List of object or the object

My lib is calling an API which can return either of the following JSON structure -
{
"key_is_same" : {
"inner" : "val"
}
}
-or-
{
"key_is_same" : [{
"inner" : "val1"
},
{
"inner" : "val2"
}
]
}
Is there any annotation in jakson which can handle this and deserializ it into respective type
Looks like you are looking for the ACCEPT_SINGLE_VALUE_AS_ARRAY deserialization feature.
Feature that determines whether it is acceptable to coerce non-array (in JSON) values to work with Java collection (arrays, java.util.Collection) types. If enabled, collection deserializers will try to handle non-array values as if they had "implicit" surrounding JSON array. This feature is meant to be used for compatibility/interoperability reasons, to work with packages (such as XML-to-JSON converters) that leave out JSON array in cases where there is just a single element in array.
Feature is disabled by default.
It could be enabled either in ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
Or via the #JsonFormat annotation:
#JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<Foo> oneOrMany;
For illustration purposes, consider the following JSON documents:
{
"oneOrMany": [
{
"value": "one"
},
{
"value": "two"
}
]
}
{
"oneOrMany": {
"value": "one"
}
}
It could be the deserialized to the following classes:
#Data
public class Foo {
private List<Bar> oneOrMany;
}
#Data
public class Bar {
private String value;
}
Just ensure the feature is enabled in your ObjectMapper or your field is annotated with #JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY).
And in case you are looking for the equivalent feature for serialization, refer to WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED.
I would recommend using Object as your data type for the property which is dynamic. So Here is my sample.
import java.util.Arrays;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class MainObject {
private Object key_is_same;
public Object getKey_is_same() {
return key_is_same;
}
public void setKey_is_same(Object key) {
this.key_is_same = key;
}
public static class KeyObject {
private String inner;
public String getInner() {
return inner;
}
public void setInner(String inner) {
this.inner = inner;
}
}
public static void main(String...s) throws JsonProcessingException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
public static void main(String...s) throws IOException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
// Deserialize
MainObject mainWithObject = om.readValue("{\"key_is_same\":{\"inner\":\"val1\"}}", MainObject.class);
MainObject mainWithList = om.readValue("{\"key_is_same\":[{\"inner\":\"val1\"},{\"inner\":\"val1\"}]}", MainObject.class);
if(mainWithList.getKey_is_same() instanceof java.util.List) {
((java.util.List) mainWithList.getKey_is_same()).forEach(System.out::println);
}
}
}
}
Output
{"key_is_same":{"inner":"val1"}}
{"key_is_same":[{"inner":"val1"},{"inner":"val1"}]}

Map a JSON field (to a value) based on another field (which is a key) using Jackson

{
"key1": {
"parameter1": "String1",
"parameter2": "String2"
},
"key2": {
"parameter1": "String3",
"parameter2": "String4"
},
"key3": {
"parameter1": "String5",
"parameter2": "String6"
}
}
I have the above JSON (/Users/user1/Desktop/responseMap.json) which is basically a Map<String, MockResponse> where MockResponse is the below POJO:
public class MockResponse {
public String parameter1;
public String parameter2;
}
Now, I have another POJO - TestCase, and another JSON - testCase.json as below:
public class TestCase {
public String responseMapFileLocation;
public String mockResponseKey;
public MockResponse mockResponse;
}
testCase.json
{
"responseMapFileLocation": "/Users/user1/Desktop/responseMap.json",
"mockResponseKey": "key1",
"mockResponse": null
}
What I am able to do is first map testCase.json to TestCase using Jackson, then map responseMap.json to Map<String, MockResponse>, then in my code search for mockResponseKey in the map.
But what I want to do is when I map testCase.json to TestCase using Jackson, I want the value of variable mockResponse to set automatically based on the value of variable mockResponseKey using the first JSON map.
You need to write custom deserialiser for TestCase class. In custom deserialiser you can parse basic properties: responseMapFileLocation, mockResponseKey and load mockResponse from other file. To deserialiser MockResponse you can use new ObjectMapper instance. Below code shows how this concept could be implemented:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.type.MapType;
import java.io.File;
import java.io.IOException;
import java.util.Map;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(jsonFile, TestCase.class));
}
}
class MockResponse {
public String parameter1;
public String parameter2;
}
#JsonDeserialize(using = TestCaseFromExternalFileDeserializer.class)
class TestCase {
public String responseMapFileLocation;
public String mockResponseKey;
public MockResponse mockResponse;
}
class TestCaseFromExternalFileDeserializer extends JsonDeserializer<TestCase> {
private final ObjectMapper mapper;
private final MapType mapType;
public TestCaseFromExternalFileDeserializer() {
mapper = new ObjectMapper();
mapType = mapper.getTypeFactory().constructMapType(Map.class, String.class, MockResponse.class);
}
#Override
public TestCase deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
TreeNode treeNode = p.readValueAsTree();
TestCase testCase = new TestCase();
testCase.responseMapFileLocation = ((JsonNode) treeNode.get("responseMapFileLocation")).asText();
testCase.mockResponseKey = ((JsonNode) treeNode.get("mockResponseKey")).asText();
parseMockResponse(testCase);
return testCase;
}
private void parseMockResponse(TestCase testCase) throws IOException {
Map<String, MockResponse> map = mapper.readValue(new File(testCase.responseMapFileLocation), mapType);
testCase.mockResponse = map.get(testCase.mockResponseKey);
}
}
You need to implement only toString method for each POJO class. Above code prints:
TestCase{responseMapFileLocation='./resource/responseMap.json', mockResponseKey='key1', mockResponse=MockResponse{parameter1='String1', parameter2='String2'}}
Both JSON files are in resource folder.
See also:
How use jackson ObjectMapper inside custom deserializer?
Jackson Streaming API - if you want to implement MockResponse deserialisation in faster way.
Tweaking the getter setter in your Test class and marking the field as private I was able to make it dynamic (Imports are from org.codehaus.jackson package)
class TestCase {
private String responseMapFileLocation;
private String mockResponseKey;
#JsonIgnore
private MockResponse mockResponse; //else value will be override in json value
public String getResponseMapFileLocation() {
return responseMapFileLocation;
}
public void setResponseMapFileLocation(String responseMapFileLocation) {
this.responseMapFileLocation = responseMapFileLocation;
}
public String getMockResponseKey() {
return mockResponseKey;
}
public void setMockResponseKey(String mockResponseKey1) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Map<String, MockResponse> map = mapper.readValue(new File("C:\\Users\\Json1.json"), TypeFactory.mapType(HashMap.class, String.class, MockResponse.class));
this.mockResponse = map.get(mockResponseKey1);
this.mockResponseKey = mockResponseKey1;
}
public MockResponse getMockResponse() {
return mockResponse;
}
#Override
public String toString() {
return "TestCase [responseMapFileLocation=" + responseMapFileLocation + ", mockResponseKey=" + mockResponseKey
+ ", mockResponse=" + mockResponse + "]";
}
}
class MockResponse {
public String parameter1;
public String parameter2;
#Override
public String toString() {
return "MockResponse [parameter1=" + parameter1 + ", parameter2=" + parameter2 + "]";
}
}
and Running below code
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
TestCase testCase = mapper.readValue(new File("C:\\UsersJson2.json"), TestCase.class);
System.out.println(testCase);
}
output will be
TestCase [responseMapFileLocation=/Users/user1/Desktop/responseMap.json, mockResponseKey=key1, mockResponse=MockResponse [parameter1=String1, parameter2=String2]]
What you are asking for is not possible with just Jackson. Jackson is primarily a marshalling/unmarshalling tool, converting JSONs to Objects and vice versa. In other words, the value of the object must be known at the time of unmarshalling.
However you can unmarshal your json as a HashMap using the code:
new JSONObject(map);
search for the MockResponse-as-a-string using the mockResponseKey and then unmarshal that code into a new MockResponse.

Removing "id" from JSON schema when creating it from POJO using jackson

How to remove the id field ("id" : "urn:jsonschema:org:gradle:Person")
from JSON schema created using Jackson?
Generated Schema
{
"type" : "object",
"id" : "urn:jsonschema:org:gradle:Person",
"properties" : {
"name" : {
"type" : "string"
}
}
}
For POJO class (Person.class)
import com.fasterxml.jackson.annotation.JsonProperty;
public class Person {
#JsonProperty("name")
private String name;
}
Using JSON Schema Generator
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator;
public final class GetJsonSchema {
public static String getJsonSchema2(Class clazz) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonSchemaGenerator jsonSchemaGenerator = new JsonSchemaGenerator(mapper);
JsonSchema jsonSchema = jsonSchemaGenerator.generateSchema(clazz);
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema);
}
}
Invoked like
System.out.println(JsonSchema.Create(Person.class));
Just set id to null.
E.g.:
jsonSchema.setId(null);
As sachin said, jsonSchema.setId(null) is a good way to accomplish your goal. But Venkat is right in that complex types will still have the id's.
One way to remove them is to use a custom SchemaFactoryWrapper, which will instantiate its own visitorContext which will refuse to provide a URN. However, it's important to note this won't work if one type refers to itself (for example, a status object that might have children status objects).
For example:
private static class IgnoreURNSchemaFactoryWrapper extends SchemaFactoryWrapper {
public IgnoreURNSchemaFactoryWrapper() {
this(null, new WrapperFactory());
}
public IgnoreURNSchemaFactoryWrapper(SerializerProvider p) {
this(p, new WrapperFactory());
}
protected IgnoreURNSchemaFactoryWrapper(WrapperFactory wrapperFactory) {
this(null, wrapperFactory);
}
public IgnoreURNSchemaFactoryWrapper(SerializerProvider p, WrapperFactory wrapperFactory) {
super(p, wrapperFactory);
visitorContext = new VisitorContext() {
public String javaTypeToUrn(JavaType jt) {
return null;
}
};
}
}
private static final String printSchema(Class c) {
try {
ObjectMapper mapper = new ObjectMapper();
IgnoreURNSchemaFactoryWrapper visitor = new IgnoreURNSchemaFactoryWrapper();
mapper.acceptJsonFormatVisitor(c, visitor);
JsonSchema schema = visitor.finalSchema();
schema.setId(null);
ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
String asString = writer.writeValueAsString(schema);
return asString;
} catch(Exception e) {
e.printStackTrace();
return null;
}
}

Categories