I have a java class representing a JSON using Jackson. All of the fields, with one exception, can be translated using no annotations at all. 1-to-1, simple translations (although some of them are nested POJOs).
#Data
#AllArgsConstructor
#NoArgsConstructor
public class MyPojo {
private String someString;
private AnotherPojo someOtherPojo;
//The problem child:
private Object value;
}
The field value which is an exception to this rule, can represent any JSON field matching value* where * is a wildcard of indefinite length. That means valueString or valueReference in JSON will be assigned to this field with the assertion that only one may be present.
{
"someString": "asdasdadasdsa",
"someOtherPojo": {
"someOtherProperty": "whatever"
},
"valueCodeableConcept": {
"text": "text value",
"coding": [
{
"code": "my-code"
}
]
}
}
Using a custom deserializer on the top-level class, I can scrape all of the fields from the root node (baseNode in the following example) that start with value and set the value field appropriately. That works great! However, in doing so, I now have to set every other field in this MyPojo class manually in my deserializer, and I have to put a custom copy of this deserializer on each POJO that uses a field like value*.
private Object parseValueX(JsonNode baseNode, DeserializationContext context) throws IOException {
//Find the concrete implementation referred to by the value[x] field
Set<String> concreteNames = new HashSet<>();
baseNode.fieldNames().forEachRemaining(name -> {
if (name.startsWith("value")) {
concreteNames.add(name);
}});
if (concreteNames.isEmpty()) {
return null;
}
if (concreteNames.size() > 1) {
throw JsonMappingException.from(context, "The field value[x] must have no more than one concrete " +
"implementation, ex: valueCode, valueCodeableConcept, valueReference");
}
String concreteName = concreteNames.stream().findFirst().orElseThrow(() -> new RuntimeException(""));
JsonNode jsonSource = baseNode.get(concreteName);
//...deserialize from jsonSource, solved, but not relevant to question...
}
To make this apply to any value* property on any POJO, I tried to move the deserializer to the value attribute in the POJO (whereas it's on the top-level resource now). The first flaw is that the deserializer isn't even invoked unless the JSON property exactly matches value. What I actually need is for the entire parent JSON resource to be passed to that field-specific deserializer, so that I may find the matching field and assign it -- OR -- I need to be able to have the deserializer on MyPojo only assign the one field value and allow the automatic deserialization to take care of the others. How do I do either of these?
For those curious about my motivation, I am implementing the HL7 FHIR Specification, which specifies generic attributes called value[x] (here's one example: https://www.hl7.org/fhir/extensibility.html#Extension) where [x] becomes the type of the resource.
I think a good fit for you problem is #JsonAnySetter. This method annotation tells Jackson to route unknown properties to it. the arg (in your case) is a Map containing the json tree of the unknown property. if I understand your code properly, the name of the value property contains the class name of the target Pojo. so once you have a class name, you can tell Jackson how to "deserialize" the map into an instance of the target class.
Here is an example based on the code from the question
public class MyPojo {
public String someString; // made properties into public for this example...
public AnotherPojo someOtherPojo;
public Object value;
#JsonAnySetter
public void setValue(String name, Object value) {
System.out.println(name + " " + value.getClass());
System.out.println(value);
// basic validation
if (name.startsWith("value") && value instanceof Map) {
String className = "com.company." + name.substring("value".length());
System.out.println(name + " " + value.getClass() + " " + className);
System.out.println(value);
try {
// nice of Jackson to be able to deserialize Map into Pojo :)
ObjectMapper mapper = new ObjectMapper();
this.value = mapper.convertValue(value, Class.forName(className));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(this.value + " " + this.value.getClass());
}
}
}
public class AnotherPojo {
public String someOtherProperty;
}
public class CodeableConcept {
public String text;
public Code[] coding;
}
public class Code {
public String code;
}
Related
I have a Bean class which I have constructed according to the response I have got. Below is my Postman response.
{
"EU": [
{
"calId": "EU",
"calDate": "2022-11-01",
"prevBusinessDay": "2022-11-01",
"nextBusinessDay": "2022-11-01",
"businessDay": true,
"monthEndBusinessDay": false
}
],
"AU": [
{
"calId": "AU",
"calDate": "2022-11-01",
"prevBusinessDay": "2022-11-01",
"nextBusinessDay": "2022-11-01",
"businessDay": true,
"monthEndBusinessDay": false
}
]
}
According to this I have constructed the Bean as follows
#Data
#AllArgsConstructor
#NoArgsConstructor
public class IndexCalendarDateResponseBean {
#JsonProperty("EU")
private List<IndexCalendarDateResponseWrapper> EU;
#JsonProperty("AU")
private List<IndexCalendarDateResponseWrapper> AU;
}
IndexCalendarDateResponseWrapper is another POJO which contains all the variables calId,calDate etc which are inside the two JSON arrays.
My issue here is every time this array names cannot be EU and AU. This depends on whatever I pass in the API request URI path parameters. If I pass something else, then that will be the JSON array variable names. So my bean will not work for that. So I wanted to construct a bean that should be dynamic in nature. What I am doing with this bean is I am using this bean to fetch an API response as follows.
IndexCalendarDateResponseBean actualRIOutput = JsonPath.from(response.extract().asInputStream()).getObject("", IndexCalendarDateResponseBean.class);
So how to construct a bean such that even if I pass say something else as path parameters say JU, BU, and SU or JU and BU anything, it will fetch the response for me? Also the JSON array variables I am passing in path parameters can also vary in quantity i.e. it can be two, three or any number of paramaters I can pass. So that also the bean should accept? Is any other bean needed for this idea to support?
So how to construct a bean such that even if I pass say something else as path parameters say JU, BU, and SU or JU and BU anything, it will fetch the response for me?
You can create a field of type Map<String, List<IndexCalendarDateResponseWrapper>> and make use of the method annotated with #JsonAnySetter to populate it.
If you need the ability to serialize this POJO into the same JSON, then you can add another method exposing the data of the Map annotated with #JsonAnyGetter.
public class IndexCalendarDateResponseBean {
private Map<String, List<IndexCalendarDateResponseWrapper>> responseWrappers = new HashMap<>();
#JsonAnySetter
public void readResponseWrappers(String key, List<IndexCalendarDateResponseWrapper> value) {
responseWrappers.put(key,value);
}
#JsonAnyGetter
public Map<String, List<IndexCalendarDateResponseWrapper>> writeResponseWrappers() {
return responseWrappers;
}
}
Also the JSON array variables I am passing in path parameters can also vary in quantity i.e. it can be two, three or any number of paramaters
When you're deserializing JSON into a POJO missing properties would be set to default values of the corresponding type (for instance, null for reference types).
When you're serializing a POJO into JSON if you don't want to include null fields you can make use of the annotation #JsonInclude providing the value of JsonInclude.Include.NON_NULL or JsonInclude.Include.NON_EMPTY (depending on your requirements).
Example:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class IndexCalendarDateResponseWrapper {
// field, constructors, etc.
}
Why not replace IndexCalendarDateResponseBean with Map<Stirg,List< IndexCalendarDateResponseWrapper>>。
private static ObjectMapper mapper = new ObjectMapper();
static {
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
}
public static <T> T parseObject(String json, TypeReference<T> typeRef) throws Exception {
return mapper.readValue(json, typeRef);
}
....
return JsonUtils.parseObject(jsonStr, new TypeReference<Map<String, List<IndexCalendarDateResponseWrapper>>>() {
});
I want to use Jackson to implement toString() to return the JSON representation of an object, but I do not want to use any Jackson annotation in my code.
I tried an implementation along the lines of:
public String toString()
{
Map<String,Object> ordered = ImmutableMap.<String, Object>builder().
put("createdAt", createdAt.toString()).
put("address", address.toString()).
build();
ObjectMapper om = new ObjectMapper();
om.enable(SerializationFeature.INDENT_OUTPUT);
try
{
return om.writeValueAsString(object);
}
catch (JsonProcessingException e)
{
// Unexpected
throw new AssertionError(e);
}
}
This works well for simple fields but if "address" has its own fields then instead of getting this:
{
"address" : {
"value" : "AZ4RPBb1kSkH4RNewi4NXNkBu7BX9DmecJ",
"tag" : null
}
I get this output instead:
{
"address" : "{\n\"value\" : \"AZ4RPBb1kSkH4RNewi4NXNkBu7BX9DmecJ\",\n \"tag\" : null"
}
In other words, the address value is being treated like a String as opposed to a JsonNode.
To clarify:
On the one hand, I want to control how simple class fields are converted to String. I don't want to use Jackson's built-in converter.
On the other hand, for complex fields, returning a String value to Jackson leads to the wrong behavior.
I believe that I could solve this problem by adding a public toJson() method to all my classes. That method would return a Map<String, JsonNode>, where the value is a string node for simple fields and the output of toJson() for complex fields. Unfortunately, this would pollute my public API with implementation details.
How can I achieve the desired behavior without polluting the class's public API?
UPDATE: I just saw an interesting answer at https://stackoverflow.com/a/9599585/14731 ... Perhaps I could convert the String value of complex fields back to JsonNode before passing them on to Jackson.
I think you should implement two methods in each class - one to dump data, second to build JSON out of raw data structure. You need to separate this, otherwise you will nest it deeper and deeper every time you encapsulate nested toString() calls.
An example:
class Address {
private BigDecimal yourField;
/* …cut… */
public Map<String, Object> toMap() {
Map<String, Object> raw = new HashMap<>();
raw.put("yourField", this.yourField.toPlainString());
/* more fields */
return raw;
}
#Override
public String toString() {
// add JSON processing exception handling, dropped for readability
return new ObjectMapper().writeValueAsString(this.toMap());
}
}
class Employee {
private Address address;
/* …cut… */
public Map<String, Object> toMap() {
Map<String, Object> raw = new HashMap<>();
raw.put("address", this.address.toMap());
/* more fields */
return raw;
}
#Override
public String toString() {
// add JSON processing exception handling, dropped for readability
return new ObjectMapper().writeValueAsString(this.toMap());
}
}
To start off, I have looked at a few other answers for similar questions, but they do not answer my particular situation.
I'm parsing JSON messages which consist of a body and a header, where the header stores what type of object the body is:
{
"body": {
"eventName": "someEventName"
},
"header": {
"purpose": "event"
}
}
In Java, I've modeled this structure using the following classes:
public class Message {
public Body body;
public Header header;
}
public class Header {
public String purpose; // Marks what child class the body of the message uses
}
public abstract class Body {
// Child classes store additional fields
}
// Example implementation of the body class
public class EventBody extends Body {
public String eventName; // Name of some event
}
After doing some research, I found that RuntimeTypeAdapterFactory is normally used to parse/write polymorphic objects; however, the RutimeTypeAdapterFactory class relies on the type being stored in the base class of the polymorphic object (i.e. Body). But in this scenario, that's not the case ― the type is stored in another object, Header.
What would be the best way to go about parsing these kind of objects? I'd like to avoid having to write a custom Serializer/Deserializer for compactness, but I wouldn't mind writing them if it's necessary.
I realize that asking for a solution that doesn't involve a custom Serializer/Deserializer is a bit ridiculous, as this is exactly the type of scenario they'd be used in (I was thinking I could get away with a custom TypeAdapterFactory, but using a Serializer/Deserializer is easier).
Anyway, for my scenario, a combination of a custom Serializer/Deserializer for the Message class seems to work fine. Since I already use an enum to track different message purposes and their string names, I decided to simply add an additional field to that enum to store the corresponding body class.
MessagePurpose Enum:
public enum MessagePurpose {
EVENT("event", EventBody.class);
public final String purposeName;
public final Class bodyClass;
MessagePurpose(String purposeName, Class classi) {
this.purposeName = purposeName;
bodyClass = classi;
}
}
MessageSerializer:
public class MessageSerializer implements JsonSerializer<Message> {
#Override
public JsonElement serialize(Message message, Type type, JsonSerializationContext jsc) {
if(message == null) {
return null;
}
JsonObject messageObj = new JsonObject();
// Get the class representing the body object from the purpose enum
Class bodyClassType = message.getPurpose().bodyClass;
messageObj.add("body", jsc.serialize(message.getBody(), bodyClassType));
messageObj.add("header", jsc.serialize(message.getHeader(), Header.class));
return messageObj;
}
}
MessageDeserializer:
public class MessageDeserializer implements JsonDeserializer<Message> {
#Override
public Message deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException {
Header header = jdc.deserialize(je.getAsJsonObject().get("header"), Header.class);
// Get the class representing the body object from the purpose enum
Class bodyClassType = header.getPurpose().bodyClass;
Body body = jdc.deserialize(je.getAsJsonObject().get("body"), bodyClassType);
return new Message(body, header);
}
}
Main function to test with:
public static void main(String[] args) {
GsonBuilder gb = new GsonBuilder();
// Register the Message class since I need to access info in the header
gb.registerTypeAdapter(Message.class, new MessageDeserializer());
gb.registerTypeAdapter(Message.class, new MessageSerializer());
Gson gson = gb.setPrettyPrinting().create();
EventBody event = new EventBody(EventType.SOME_EVENT_NAME);
String eventJson = gson.toJson(event.getAsMessage());
System.out.println(eventJson);
Message newEvent = gson.fromJson(eventJson);
System.out.println("\nEvent type: " + ((EventBody) newEvent.getBody()).getEventName());
}
The above test class prints:
{
"body": {
"eventType": "someEventName"
},
"header": {
"purpose": "event"
}
}
Event Type: someEventName
This output matches the JSON of the Messages I'm parsing, and it seems to deserialize different types of messages just fine.
What happens if I annotate a constructor parameter using #JsonProperty but the Json doesn't specify that property. What value does the constructor get?
How do I differentiate between a property having a null value versus a property that is not present in the JSON?
Summarizing excellent answers by Programmer Bruce and StaxMan:
Missing properties referenced by the constructor are assigned a default value as defined by Java.
You can use setter methods to differentiate between properties that are implicitly or explicitly set. Setter methods are only invoked for properties with explicit values. Setter methods can keep track of whether a property was explicitly set using a boolean flag (e.g. isValueSet).
What happens if I annotate a constructor parameter using #JsonProperty but the Json doesn't specify that property. What value does the constructor get?
For questions such as this, I like to just write a sample program and see what happens.
Following is such a sample program.
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.ObjectMapper;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
ObjectMapper mapper = new ObjectMapper();
// {"name":"Fred","id":42}
String jsonInput1 = "{\"name\":\"Fred\",\"id\":42}";
Bar bar1 = mapper.readValue(jsonInput1, Bar.class);
System.out.println(bar1);
// output:
// Bar: name=Fred, id=42
// {"name":"James"}
String jsonInput2 = "{\"name\":\"James\"}";
Bar bar2 = mapper.readValue(jsonInput2, Bar.class);
System.out.println(bar2);
// output:
// Bar: name=James, id=0
// {"id":7}
String jsonInput3 = "{\"id\":7}";
Bar bar3 = mapper.readValue(jsonInput3, Bar.class);
System.out.println(bar3);
// output:
// Bar: name=null, id=7
}
}
class Bar
{
private String name = "BLANK";
private int id = -1;
Bar(#JsonProperty("name") String n, #JsonProperty("id") int i)
{
name = n;
id = i;
}
#Override
public String toString()
{
return String.format("Bar: name=%s, id=%d", name, id);
}
}
The result is that the constructor is passed the default value for the data type.
How do I differentiate between a property having a null value versus a property that is not present in the JSON?
One simple approach would be to check for a default value post deserialization processing, since if the element were present in the JSON but had a null value, then the null value would be used to replace any default value given the corresponding Java field. For example:
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;
import org.codehaus.jackson.map.ObjectMapper;
public class JacksonFooToo
{
public static void main(String[] args) throws Exception
{
ObjectMapper mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY);
// {"name":null,"id":99}
String jsonInput1 = "{\"name\":null,\"id\":99}";
BarToo barToo1 = mapper.readValue(jsonInput1, BarToo.class);
System.out.println(barToo1);
// output:
// BarToo: name=null, id=99
// {"id":99}
String jsonInput2 = "{\"id\":99}";
BarToo barToo2 = mapper.readValue(jsonInput2, BarToo.class);
System.out.println(barToo2);
// output:
// BarToo: name=BLANK, id=99
// Interrogate barToo1 and barToo2 for
// the current value of the name field.
// If it's null, then it was null in the JSON.
// If it's BLANK, then it was missing in the JSON.
}
}
class BarToo
{
String name = "BLANK";
int id = -1;
#Override
public String toString()
{
return String.format("BarToo: name=%s, id=%d", name, id);
}
}
Another approach would be to implement a custom deserializer that checks for the required JSON elements. And yet another approach would be to log an enhancement request with the Jackson project at http://jira.codehaus.org/browse/JACKSON
In addition to constructor behavior explained in #Programmer_Bruce's answer, one way to differentiate between null value and missing value is to define a setter: setter is only called with explicit null value.
Custom setter can then set a private boolean flag ("isValueSet" or whatever) if you want to keep track of values set.
Setters have precedence over fields, in case both field and setter exist, so you can "override" behavior this way as well.
I'm thinking of using something in the style of an Option class, where a Nothing object would tell me if there is such a value or not. Has anyone done something like this with Jackson (in Java, not Scala, et al)?
(My answer might be useful to some people finding this thread via google, even if it doesn't answer OPs question)
If you are dealing with primitive types which are omittable, and you do not want to use a setter like described in the other answers (for example if you want your field to be final), you can use box objects:
public class Foo {
private final int number;
public Foo(#JsonProperty Integer number) {
if (number == null) {
this.number = 42; // some default value
} else {
this.number = number;
}
}
}
this doesn't work if the JSON actually contains null, but it can be sufficient if you know it will only contain primitives or be absent
another option is to validate the object after deserialization either manually or via frameworks such java bean validation or, if you are using spring, the spring validation support.
I use FlexJson for serialization, the only problem is that it generates the field names lower case while I need them to start with upper case:
class Person
{
String name;
public String getName() { return name;}
}
When serialized the field is serialized as name, while I need it to be Name.
How can I specify the output field name? Is there some attribute I can put to specify the required serialization name?
You can achieve this by using a Custom Transformer. As per Flexjson page transformer is:
Responsible for deciding how to translate the passed in object to
JSON, making the appropriate calls on the JSONContext object to output
the JSON, and/or passing the object along the transformation process.
Flexjson has provided an abstract class AbstractTransformer for this purpose; Extend and override transform(Object object) to handle the transformation by yourself.
Pasted below is the code of FieldNameTransformer which I wrote for specifying the field name s manually:
public class FieldNameTransformer extends AbstractTransformer {
private String transformedFieldName;
public FieldNameTransformer(String transformedFieldName) {
this.transformedFieldName = transformedFieldName;
}
public void transform(Object object) {
boolean setContext = false;
TypeContext typeContext = getContext().peekTypeContext();
//Write comma before starting to write field name if this
//isn't first property that is being transformed
if (!typeContext.isFirst())
getContext().writeComma();
typeContext.setFirst(false);
getContext().writeName(getTransformedFieldName());
getContext().writeQuoted(object.toString());
if (setContext) {
getContext().writeCloseObject();
}
}
/***
* TRUE tells the JSONContext that this class will be handling
* the writing of our property name by itself.
*/
#Override
public Boolean isInline() {
return Boolean.TRUE;
}
public String getTransformedFieldName() {
return this.transformedFieldName;
}
}
Following is how to use this custom transformer:
JSONSerializer serializer = new JSONSerializer().transform(new FieldNameTransformer("Name"), "name");
where original field's name is 'name' but in json ouput it will be replaced with Name.
Sample out:
{"Name":"Abdul Kareem"}