How to serialize json to Map? - java

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));
}

Related

Jackson How to add additional properties during searlization without making changes to default POJO?

I am using Jackson-ObjectMapper to create the JSON data according to my POJO. I would like to add some additional properties to my JSON without modifying the POJO.
Since the POJO has been added as a dependency in my project I cannot modify it but I need some additional fields in JSON. Is there a way to add new key-value pair to JSON without making modifications to Java POJO? I am currently using Jackson 2.13.2 latest version dependencies: jackson-core, jackson-databind, jackson-annotations, jackson-datatype-jdk8
Following is the Java code:
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.Setter;
public class Test {
public static void main(String args[]) throws Exception{
ObjectMapper objectMapper = new ObjectMapper();
CustomClass cc = new CustomClass();
cc.setName("Batman");
cc.setAge(30);
System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(cc));
}
#Getter
#Setter
public static class CustomClass {
private String name;
private int age;
}
}
This is providing me with the JSON:
{
"name" : "Batman",
"age" : 30
}
I would like to obtain the JSON that looks something like this, but do not want to add new fields job and company to my CustomClass POJO.
{
"name" : "Batman",
"age" : 30,
"job" : "HR",
"company": "New"
}
I tried to do something like this: https://www.jdoodle.com/a/z99 but I get the error: Type id handling not implemented for type package.ClassName (by serializer of type package.CustomModule$CustomClassSerializer)
You can try like below in the controller level, (it would be better to manipulate the response by using the Interceptor or Filter)
#GetMapping
public ObjectNode test() throws JsonProcessingException {
CustomClass customClass = new CustomClass();
customClass.setAge(30);
customClass.setName("Batman");
ObjectMapper mapper = new ObjectMapper();
String jsonStr = mapper.writeValueAsString(customClass);
ObjectNode nodes = mapper.readValue(jsonStr, ObjectNode.class);
nodes.put("job", "HR ");
nodes.put("company", "New ");
return nodes;
}
Response:
{
name: "Batman",
age: 30,
job: "HR ",
company: "New "
}
Your new test driver is below,
public static void main(String[] args) throws JsonProcessingException {
CustomClass customClass = new CustomClass();
customClass.setAge(30);
customClass.setName("Batman");
ObjectMapper mapper = new ObjectMapper();
String jsonStr = mapper.writeValueAsString(customClass);
ObjectNode nodes = mapper.readValue(jsonStr, ObjectNode.class);
nodes.put("job", "HR ");
nodes.put("company", "New ");
System.out.println(nodes);
}
Output: {"name":"Batman","age":30,"job":"HR ","company":"New "}
Updated
but I get the error: Type id handling not implemented for type
package.ClassName (by serializer of type
package.CustomModule$CustomClassSerializer)
Write new object fields "job" and "company" to your custom class serializer.
public class Test {
public static void main(String args[]) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new CustomModule());
CustomClass cc = new CustomClass();
cc.setAge(30);
cc.setName("Batman");
StringWriter sw = new StringWriter();
objectMapper.writeValue(sw, cc);
System.out.println(sw.toString());
}
public static class CustomModule extends SimpleModule {
public CustomModule() {
addSerializer(CustomClass.class, new CustomClassSerializer());
}
private static class CustomClassSerializer extends JsonSerializer {
#Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
// Validate.isInstanceOf(CustomClass.class, value);
jgen.writeStartObject();
JavaType javaType = provider.constructType(CustomClass.class);
BeanDescription beanDesc = provider.getConfig().introspect(javaType);
JsonSerializer<Object> serializer = BeanSerializerFactory.instance.findBeanSerializer(provider,
javaType, beanDesc);
// this is basically your 'writeAllFields()'-method:
serializer.unwrappingSerializer(null).serialize(value, jgen, provider);
jgen.writeObjectField("job", "HR ");
jgen.writeObjectField("company", "New ");
jgen.writeEndObject();
}
}
}
}
Output: {"name":"Batman","age":30,"job":"HR ","company":"New "}

How I parse Color java class to JSON with Jackson?

I am trying to deserialise the Color class from JSON with Jackson but it throws exception:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "colorSpace" (class java.awt.Color), not marked as
ignorable.
What i'm doing wrong?
This is my code:
File act = new File(new File().getAbsolutePath());
ObjectMapper om = new ObjectMapper();
File f = new File(act, "123.JSON");
om.writeValue(f, new person());
person per = om.readValue(f, person.class);
System.out.println(per);
This is my person class:
public class person implements Serializable {
//it include getters, setters and builder
String nombe = "Pepe";
String CI = "12345678978";
Color c = Color.red;
}
java.awt.Color class is not a regular POJO or Enum. You need to implement custom serialiser and deserialiser if you want to store it in JSON format. Color class can be represented by its RGB representation and you can store it as a number:
class ColorJsonSerializer extends JsonSerializer<Color> {
#Override
public void serialize(Color value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value == null) {
gen.writeNull();
return;
}
gen.writeNumber(value.getRGB());
}
}
class ColorJsonDeserializer extends JsonDeserializer<Color> {
#Override
public Color deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return new Color(p.getValueAsInt());
}
}
Simple usage:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.awt.*;
import java.io.IOException;
public class JsonPathApp {
public static void main(String[] args) throws Exception {
SimpleModule awtModule = new SimpleModule("AWT Module");
awtModule.addSerializer(Color.class, new ColorJsonSerializer());
awtModule.addDeserializer(Color.class, new ColorJsonDeserializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(awtModule);
String json = mapper.writeValueAsString(new Person());
System.out.println(json);
System.out.println(mapper.readValue(json, Person.class));
}
}
Above code prints:
{"nombe":"Pepe","c":-65536,"ci":"12345678978"}
Person{nombe='Pepe', CI='12345678978', c=java.awt.Color[r=255,g=0,b=0]}
Take a look on similar question where Color is stored as JSON object:
Unable to deserialize java.awt.color using jackson deserializer

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.

How do I configure Jackson ObjectMapper to deserialize all fields of type Foo into instances of SubclassOfFoo?

I'm deserializing a large json value. Deeply nested within that value is a json object like the following:
{
"fieldOne": "valueOne",
"fieldTwo": {
"innerField": "innerValue"
}
}
I'm using the Jackson ObjectMapper to deserialize the large json value into a 3rd party class. Deeply nested within that 3rd party class is another 3rd party class:
public class DeepThirdPartyClass {
public String fieldOne;
}
which unfortunately is missing the fieldTwo property. I can create my own class which adds the missing field:
public class MyClass extends DeepThirdPartyClass {
public MySubObject fieldTwo;
}
How do I configure jackson so that whenever it attempts to deserialize a value to DeepThirdPartyClass, it deserializes to MyClass instead?
I had similar requirement when I have to filter any not allowed characters in all String values.
To create Object Mapper:
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
super();
SimpleModule module = new SimpleModule("HTML XSS Serializer", new Version(1, 0, 0, "FINAL", "com.crowdoptic", "web"));
module.addSerializer(String.class, new JsonHtmlXssSerializer());
module.addDeserializer(String.class, new JsonHtmlXssDeserializer());
this.registerModule(module);
}
}
public class JsonHtmlXssDeserializer extends StdScalarDeserializer<String> {
private static final Logger LOG = LogManager.getLogger(JsonHtmlXssDeserializer.class);
public JsonHtmlXssDeserializer() { super(String.class); }
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = StringDeserializer.instance.deserialize(p, ctxt);
LOG.trace("in deserialize for value: " + value);
String encodedValue = StringEscapeUtils.escapeHtml4(value);
return encodedValue;
}
#Override
public String deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
return StringDeserializer.instance.deserializeWithType(jp, ctxt, typeDeserializer);
}
#Override
public boolean isCachable() { return StringDeserializer.instance.isCachable(); }
}
In your case you can register your class deserializer, call super method of the object deserializer. Then instead of returning DeepThirdPartyClass, create object of MyClass, set field one from DeepThirdPartyClass and add second field. See StringDeserializer and others for implementation details and available properties.
Let me know if that helps.
I reworked #olga-khylkouskaya's solution to fit my problem:
#Test
public void newDeserializer() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule("DeepThirdPartyClass subclass override", new Version(1, 0, 0, "FINAL", "com.example", "deep-third-party-class-override"));
module.addDeserializer(DeepThirdPartyClass.class, new JsonDeserializer<DeepThirdPartyClass>() {
#Override
public DeepThirdPartyClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return p.readValueAs(MyClass.class);
}
});
objectMapper.registerModule(module);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
String json = "{\n" +
" \"middle\": {\n" +
" \"fieldOne\": \"valueOne\",\n" +
" \"fieldTwo\": {\n" +
" \"fieldThree\": \"valueThree\"\n" +
" }\n" +
" }\n" +
"}\n";
ThirdPartyClass thirdPartyClass = objectMapper.readValue(json, ThirdPartyClass.class);
}
public class ThirdPartyClass {
public DeepThirdPartyClass middle;
}
public class InnerClass {
public String fieldThree;
}

How to delegate to default deserialization in custom deserializer in Jackson?

Suppose I am writing custom serialization for some class, but would like to process one of its field with default methods.
How to do that?
While serializing we have JsonGenerator#writeObjectField().
But what is corresponding method for deserialization?
Regard the code below:
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.io.IOException;
import java.util.Objects;
public class TryDelegate {
public static class MyOuterClassSerializer extends JsonSerializer<MyOuterClass> {
#Override
public void serialize(MyOuterClass value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
gen.writeStartObject();
gen.writeObjectField("inner", value.getInner());
gen.writeEndObject();
}
}
public static class MyOuterClassDeserializer extends JsonDeserializer<MyOuterClass> {
#Override
public MyOuterClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
MyOuterClass ans = new MyOuterClass();
JsonToken token;
token = p.getCurrentToken();
if( token != JsonToken.START_OBJECT ) {
throw new JsonParseException("Start object expected", p.getCurrentLocation());
}
if( !"inner".equals(p.nextFieldName() ) ) {
throw new JsonParseException("'inner; field expected", p.getCurrentLocation());
}
MyInnerClass inner = null;// how to desrialize inner from here with default processing???
ans.setInner(inner);
token = p.nextToken();
if( token != JsonToken.END_OBJECT ) {
throw new JsonParseException("End object expected", p.getCurrentLocation());
}
return ans;
}
}
public static class MyInnerClass {
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
#Override
public String toString() {
return "{\"value\":" + value + "}";
}
}
#JsonDeserialize(using = MyOuterClassDeserializer.class)
#JsonSerialize(using = MyOuterClassSerializer.class)
public static class MyOuterClass {
private MyInnerClass inner;
public MyInnerClass getInner() {
return inner;
}
public void setInner(MyInnerClass inner) {
this.inner = inner;
}
#Override
public String toString() {
return "{\"inner\":" + Objects.toString(inner) + "}";
}
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
String string;
MyInnerClass inner = new MyInnerClass();
inner.setValue(12);
MyOuterClass outer = new MyOuterClass();
outer.setInner(inner);
string = mapper.writeValueAsString(outer);
System.out.println(string);
MyOuterClass outer2 = mapper.readValue(string, MyOuterClass.class);
System.out.println(outer2); // inner was not deserialized
}
}
How to implement MyOuterDeserializer?
The DeserializationContext offers these tools.
After checking the field name for "inner", move to the next token, the beginning of the JSON object and use the DeserializationContext to deserialize the JSON object into a MyInnerClass object.
if (!"inner".equals(p.nextFieldName())) {
throw new JsonParseException("'inner; field expected", p.getCurrentLocation());
}
p.nextToken(); // consumes the field name token
MyInnerClass inner = ctxt.readValue(p, MyInnerClass.class);
The javadoc states
Convenience method that may be used by composite or container
deserializers, for reading one-off values contained (for sequences, it
is more efficient to actually fetch deserializer once for the whole
collection).
Careful while using the DeserializationContext. Don't try to recursively deserialize types for which you have have registered custom deserializers.

Categories