At a certain point in my code, I have parse a JSON document, represented as a string, to a JsonNode, because I don't know yet the actual target pojo class type.
Now, some time later, I know the Class instance of the pojo and I want to convert this JsonNode to an actual pojo of that class (which is annotated with the proper #JsonProperty annotations). Can this be done? If so, how?
I am working with Jackson 2.10.x.
In this case you can use two methods:
treeToValue
convertValue
See below example:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.util.StringJoiner;
public class JsonNodeConvertApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(jsonFile);
System.out.println(mapper.treeToValue(node, Message.class));
System.out.println(mapper.convertValue(node, Message.class));
}
}
class Message {
private int id;
private String body;
// getters, setters, toString
}
Above code for JSON payload like below:
{
"id": 1,
"body": "message body"
}
prints:
Message[id=1, body='message body']
Message[id=1, body='message body']
Related
I have a Java record with one field only:
public record AggregateId(UUID id) {}
And a class with the AggregateId field (other fields removed for readability)
public class Aggregate {
public final AggregateId aggregateId;
#JsonCreator
public Aggregate(
#JsonProperty("aggregateId") AggregateId aggregateId
) {
this.aggregateId = aggregateId;
}
}
The implementation above serialize and deserialize JSON with given example:
ObjectMapper objectMapper = new ObjectMapper();
String content = """
{
"aggregateId": {
"id": "3f61aede-83dd-4049-a6ff-337887b6b807"
}
}
""";
Aggregate aggregate = objectMapper.readValue(content, Aggregate.class);
System.out.println(objectMapper.writeValueAsString(aggregate));
How could I change Jackson config to replace JSON by that:
{
"aggregateId": "3f61aede-83dd-4049-a6ff-337887b6b807"
}
without giving up a separate class for AggregateId and access through fields, without getters?
I tried #JsonUnwrapper annotation, but this caused throws
Exception in thread "X" com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Invalid type definition for type `X`:
Cannot define Creator parameter as `#JsonUnwrapped`: combination not yet supported at [Source: (String)"{
"aggregateId": "3f61aede-83dd-4049-a6ff-337887b6b807"
}"
or
Exception in thread "X" com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot define Creator property "aggregateId" as `#JsonUnwrapped`:
combination not yet supported at [Source: (String)"{
"aggregateId": "3f61aede-83dd-4049-a6ff-337887b6b807"
}"
Jackson version: 2.13.1
dependencies {
compile "com.fasterxml.jackson.core:jackson-annotations:2.13.1"
compile "com.fasterxml.jackson.core:jackson-databind:2.13.1"
}
Of course, it's possible with a custom serializer/deserializer, but I'm looking for an easier solution because I have many different classes with a similar issue.
The combination of #JsonUnwrapped and #JsonCreator is not supported yet, so we can generate a solution like this:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.util.UUID;
public class AggregateTest {
static record AggregateId(#JsonProperty("aggregateId") UUID id) {}
static class Aggregate {
#JsonUnwrapped
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
public final AggregateId _aggregateId;
public final String otherField;
#JsonCreator
public Aggregate(#JsonProperty("aggregateId") UUID aggregateId,
#JsonProperty("otherField") String otherField) {
this._aggregateId = new AggregateId(aggregateId);
this.otherField = otherField;
}
}
public static void main(String[] args) throws JsonProcessingException {
String rawJson =
"{\"aggregateId\": \"1f61aede-83dd-4049-a6ff-337887b6b807\"," +
"\"otherField\": \"İsmail Y.\"}";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
Aggregate aggregate = objectMapper
.readValue(rawJson, Aggregate.class);
System.out.println(objectMapper
.writeValueAsString(aggregate));
}
}
Here we briefly get rid of the #JsonUnwrapped field.
We get the UUID with the name aggregateId and create an AggregateId record.
Detailed explanations about it:
https://github.com/FasterXML/jackson-databind/issues/1467
https://github.com/FasterXML/jackson-databind/issues/1497
I am calling an endpoint in which it returns an object. In this object it contains some fields and also a field of another type of object.
E.g.
class ResponseObject{
private final boolean success;
private final String message;
private final DifferentType different;
}
I am calling the endpoint via RestTemplate:
private LogonResponseMessage isMemberAuthenticated(UserCredentialDomain userCredentialDomain)
{
RestTemplate restTemplate = new RestTemplate();
return restTemplate.getForObject(
"http://abc.local:8145/xyz/member/authenticate/{memberLoginName}/{password}", ResponseObject.class,
userCredentialDomain.getUsername(), userCredentialDomain.getPassword());
}
So my consuming app is giving this error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `fgh.thg.member.DifferentTypeABC` (no Creators, like default constructor, exist): cannot deserialize from Object v
alue (no delegate- or property-based Creator)
I am aware that it's telling me to put a default constructor in the DifferentTypeABC class in order for jackson to deserialise it but i can't do that easily as I'll need to update the dependency on apps that the DifferentTypeABC is in across several different repos.
So i was wondering if there is a way of configuring RestTemplate or jackson on the consuming app so that it ignores attempting to deserialise objects if it doesn't contain a default constructor? To be honest, I am pretty much interested in the success and message fields on the response object.
Another way to solve the problem is by enhancing DifferentType in this module using Jackson creator Mixin. Below is an example:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(mapper.getVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
mapper.addMixIn(DifferentType.class, DifferentTypeMixin.class);
String raw = "{\"message\": \"ok\", \"differentType\": {\"name\": \"foo bar\"}}";
ResponseObject object = mapper.readValue(raw, ResponseObject.class);
System.out.println(mapper.writeValueAsString(object));
}
#Data
static class ResponseObject {
private String message;
private DifferentType differentType;
}
static class DifferentType {
private String name;
public DifferentType(String name) {
this.name = name;
}
}
public static abstract class DifferentTypeMixin {
#JsonCreator
DifferentTypeMixin(#JsonProperty("name") String name) {
}
}
}
I am not able to unmarshall a JSON key which can hold either a string value or an another JSON Object using Jackson Library.
Ex:- Below are the two possible values.
1)
"ProviderData": {
"INVALID": "HEX",
"#text": "Sample"
}
2)
"ProviderData": "1C"
Could someone please verify and suggest me on this issue.
You can write custom deserialiser and handle these both cases or write two constructors for ProviderData POJO class and properly use JsonCreator and JsonCreator annotations. See below example:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
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, Response.class));
}
}
class Response {
#JsonProperty("ProviderData")
private ProviderData data;
// getters, setters, toString
}
class ProviderData {
private static final String INVALID_NAME = "INVALID";
private static final String TEXT_NAME = "#text";
#JsonProperty(INVALID_NAME)
private final String invalid;
#JsonProperty(TEXT_NAME)
private final String text;
#JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public ProviderData(String invalid) {
this(invalid, null);
}
#JsonCreator
public ProviderData(#JsonProperty(INVALID_NAME) String invalid, #JsonProperty(TEXT_NAME) String text) {
this.invalid = invalid;
this.text = text;
}
// getters, toString
}
For this JSON payload:
{
"ProviderData": {
"INVALID": "HEX",
"#text": "Sample"
}
}
Above example prints:
Response{data=ProviderData{invalid='HEX', text='Sample'}}
And for String primitive JSON payload:
{
"ProviderData": "1C"
}
Above example prints:
Response{data=ProviderData{invalid='1C', text='null'}}
As you can see, JSON Object is mapped properly using 2-arg constructor and String primitive is mapped using 1-arg constructor and we assume that this value means invalid key from JSON Object example.
See also:
Custom JSON Deserialization with Jackson.
sequentially deserialize using Jackson.
Deserialize strings and objects using jackson annotations in java.
you could deserialize to JsonNode and then extract the contents individually, or deserialize to an Object and use instanceof to determine if it's a Map or another type, or use a custom deserializer to unpack the data into a custom object that handles both cases.
I used avro-tools to generate java classes from avsc files, using:
java.exe -jar avro-tools-1.7.7.jar compile -string schema myfile.avsc
Then I tried to serialize such objects to json by ObjectMapper,
but always got a JsonMappingException saying "not an enum" or "not a union".
In my test I create the generated object using it's builder or constructor.
I got such exceptions for objects of different classes...
Sample Code:
ObjectMapper serializer = new ObjectMapper(); // com.fasterxml.jackson.databind
serializer.register(new JtsModule()); // com.bedatadriven.jackson.datatype.jts
...
return serializer.writeValueAsBytes(avroConvertedObject); // => JsonMappingException
I also tried many configurations using: serializer.configure(...) but still failed.
Versions: Java 1.8, jackson-datatype-jts 2.3,
jackson-core 2.6.5, jackson-databind 2.6.5, jackson-annotations 2.6.5
Any suggestions?
Thanks!
If the SCHEMA member is really the case (we don't see the full error message), then you can switch it off. I use a mixin to do it, like this:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.apache.avro.Schema;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
public class AvroGenerTests
{
abstract class IgnoreSchemaProperty
{
// You have to use the correct package for JsonIgnore,
// fasterxml or codehaus
#JsonIgnore abstract void getSchema();
}
#Test
public void writeJson() throws IOException {
BookAvro b = BookAvro.newBuilder()
.setTitle("Wilk stepowy")
.setAuthor("Herman Hesse")
.build();
ObjectMapper om = new ObjectMapper();
om.enable(SerializationFeature.INDENT_OUTPUT);
om.addMixIn(BookAvro.class, IgnoreSchemaProperty.class);
om.writeValue(new File("plik_z_gen.json"), b);
}
}
My req'ts got changed on me and I was told I needed to convert Avro objects straight to JSON without preserving any of the meta-data. My other answer herein that specified a method convertToJsonString converts the entire Avro object to JSON so that using a de-encoder you can re-create the original Avro object as an Avro object. That isn't what my mgt. wanted anymore so I was back to the old drawing board.
As a Hail Mary pass I tried using Gson and it works to do what I now had to do. It's very simple:
Gson gson = new Gson();
String theJsonString = gson.toJson(object_ur_converting);
And you're done.
2022 Avro field names
abstract class IgnoreSchemaPropertyConfig {
// You have to use the correct package for JsonIgnore,
// fasterxml or codehaus
#JsonIgnore
abstract void getClassSchema();
#JsonIgnore
abstract void getSpecificData();
#JsonIgnore
abstract void get();
#JsonIgnore
abstract void getSchema();
}
After finding the code example at https://www.programcreek.com/java-api-examples/?api=org.apache.avro.io.JsonEncoder I wrote a method that should convert any given Avro object (they extend GenericRecord) to a Json String. Code:
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.io.EncoderFactory;
import org.apache.avro.io.JsonEncoder;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
// ... Class header etc. ...
public static <T extends GenericRecord> String convertToJsonString(T event) throws IOException {
String jsonstring = "";
try {
DatumWriter<T> writer = new GenericDatumWriter<T>(event.getSchema());
OutputStream out = new ByteArrayOutputStream();
JsonEncoder encoder = EncoderFactory.get().jsonEncoder(event.getSchema(), out);
writer.write(event, encoder);
encoder.flush();
jsonstring = out.toString();
} catch (IOException e) {
log.error("IOException occurred.", e);
throw e;
}
return jsonstring;
}
The previous post answers the question correctly. I am just adding on to the previous answer. Instead of writing it to a file I converted it to a string before sending it as body in a POST request.
public class AvroGenerateJSON
{
abstract class IgnoreSchemaProperty
{
// You have to use the correct package for JsonIgnore,
// fasterxml or codehaus
#JsonIgnore abstract void getSchema();
}
public String convertToJson() throws IOException {
BookAvro b = BookAvro.newBuilder()
.setTitle("Wilk stepowy")
.setAuthor("Herman Hesse")
.build();
ObjectMapper om = new ObjectMapper();
om.enable(SerializationFeature.INDENT_OUTPUT);
om.addMixIn(BookAvro.class, IgnoreSchemaProperty.class);
String jsonString = om.writeValueAsString(b);
return jsonString;
}
}
Agree with Shivansh's answer. To add, there might be instances where we need to use the avro-generated pojo in other classes. Under the hood, spring uses jackson library in handling this so we need to override global jackson config by adding a class
#Configuration
public class JacksonConfiguration {
public abstract IgnoreSchemaProperty {
#JsonIgnore abstract void getSchema();
}
#Bean
public ObjectMapper objectMapper() {
ObjectMapper om = new ObjectMapper();
om.addMixIn(SpecificRecordBase.class, IgnoreSchemaProperty.class);
return om;
}
}
SpecificRecordBase -if we want to ignore the schema field from all avro generated classes. In this way, we can serialize/deserialize our avro classes and use it anywhere in our application without getting the issue.
Using Java 6, Tomcat 7, Jersey 1.15, Jackson 2.0.6 (from FasterXml maven repo), & www.json.org parser, I am trying to
pretty print the JSON String so it will look indented by the curl -X GET command line.
I created a simple web service which has the following architecture:
My POJOs (model classes):
Family.java
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Family {
private String father;
private String mother;
private List<Children> children;
// Getter & Setters
}
Children.java
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Children {
private String name;
private String age;
private String gender;
// Getters & Setters
}
Using a Utility Class, I decided to hard code the POJOs as follows:
public class FamilyUtil {
public static Family getFamily() {
Family family = new Family();
family.setFather("Joe");
family.setMother("Jennifer");
Children child = new Children();
child.setName("Jimmy");
child.setAge("12");
child.setGender("male");
List<Children> children = new ArrayList<Children>();
children.add(child);
family.setChildren(children);
return family;
}
}
My web service:
import java.io.IOException;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jettison.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import com.myapp.controller.myappController;
import com.myapp.resource.output.HostingSegmentOutput;
import com.myapp.util.FamilyUtil;
#Path("")
public class MyWebService {
#GET
#Produces(MediaType.APPLICATION_JSON)
public static String getFamily() throws IOException,
JsonGenerationException,
JsonMappingException,
JSONException,
org.json.JSONException {
ObjectMapper mapper = new ObjectMapper();
String uglyJsonString = mapper.writeValueAsString(FamilyUtil.getFamily());
System.out.println(uglyJsonString);
JSONTokener tokener = new JSONTokener(uglyJsonString);
JSONObject finalResult = new JSONObject(tokener);
return finalResult.toString(4);
}
}
When I run this using:
curl -X GET http://localhost:8080/mywebservice
I get this in my Eclipse's console:
{"father":"Joe","mother":"Jennifer","children":[{"name":"Jimmy","age":"12","gender":"male"}]}
But from the curl command on the command line (this response is more important):
"{\n \"mother\": \"Jennifer\",\n \"children\": [{\n \"age\": \"12\",\n \"name\": \"Jimmy\",\n \"gender\": \"male\"\n }],\n \"father\": \"Joe\"\n}"
This is adding newline escape sequences and placing double quotes (but not indenting like it should it does have 4 spaces after the new line but its all in one line).
Would appreciate it if someone could point me in the right direction.
What I believe is happening is your currently configured message body reader is taking your String returned from your method, and escaping it properly so that it is a valid JSON string (since json doesn't let newlines inside of string constants).
Here's what you do... I'm assuming that you are using Jackson's included Message Body Writers. (e.g. JacksonJsonProvider)
You create a #Provider that sets up an ObjectMapper instance with Pretty Printing enabled like so:
#Provider
public class JacksonObjectMapperProvider implements ContextResolver<ObjectMapper> {
/**
* {#inheritDoc}
*/
#Override
public ObjectMapper getContext(final Class<?> type) {
final ObjectMapper toReturn = new ObjectMapper();
toReturn.enable(SerializationFeature.INDENT_OUTPUT); // This is the important setting
toReturn.disable(MapperFeature.USE_ANNOTATIONS); // I have this one on but it's probably for other resources in the container testing it in, I don't know if you'd need it.
return toReturn;
}
}
You then have your Resource return the resolved Family object instead of trying to transform it to Json... let the Message Body Writer do that... i.e.
public class MyWebService {
#GET
#Produces(MediaType.APPLICATION_JSON)
public Family getFamily()
return FamilyUtil.getFamily()
}
}
and Voila:
$ curl http://<server>/<ctx-root>/<path>
{
"father" : "Joe",
"mother" : "Jennifer",
"children" : [ {
"name" : "Jimmy",
"age" : "12",
"gender" : "male"
} ]
}
Now I glossed over getting the Provider and MessageBodyReader registered with your JAX-RS Application configuration, but that could vary greatly depending upon if you're using Jersey's servlet, using a custom Application, using Guice, or any number of other ways of setting up your JAX-RS stack.
Just use GSON library
This is an example for Jersey
normal Gson:
Gson gson = new Gson();
String json = gson.toJson(obj);
System.out.println(json);
the JSON output is display as compact mode like following :
{"data1":100,"data2":"hello","list":["String 1","String 2","String 3"]}
To enable pretty print, you should use GsonBuilder return a Gson object :
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String json = gson.toJson(obj);
System.out.println(json);
Output will like this:
{
"data1": 100,
"data2": "hello",
"list": [
"String 1",
"String 2",
"String 3"
]
}
Full example from mkyong
package com.mkyong.core;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class GsonExample {
public static void main(String[] args) {
DataObject obj = new DataObject();
// Gson gson = new Gson();
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String json = gson.toJson(obj);
System.out.println(json);
}
}