How can I read an embedded class with Jackson - java

I have the following JSON, which is a generic wrapper for Messages. From the subject I can determine what the contents are.
{
"subject" : "P:WORKSPACE:ADDED",
"msgType" : "FileInfo[]",
"contents" : [ {
"lastModified" : 1380552566000,
"name" : "genSPI.vhd.pshdl",
"size" : 630,
"syntax" : "unknown",
"type" : "pshdl"
} ]
}
Now when I read the Object with an objectReader, the contents will be a generic ArrayList with embedded Maps as the objectReader does not know what to do with the contents. That is ok for me. But how can I create a class from the contents later on? I don't want to use the polymorphic feature of Jackson as the classes that Message can contain are not known statically.
The solution I found so far appears rather clumsy to me:
final Object json = message.getContents();
final String jsonString = writer.writeValueAsString(json);
final FileInfo[] readValues = mapper.readValue(jsonString, FileInfo[].class);

Related

Gson - parsing a json that has different field names and number of fields

I'm having trouble creating a pojo from this json:
"versionAudio": {
"Simulcast": [
"English",
"Japanese"
]
},
{
"Uncut": [
"English",
"Japanese"
]
}
The versionAudio object can contain any number of String arrays and the name can be anything. I know I can't create a class for versionAudio. It will have to be an JSONObject that I will pull data out of. I'm not sure how begin. I couldn't find any SO questions that relate to this. Most cases, the name of the field is known.
Your versionAudio class should to have one field Map<String, String[]>, try use it

Spring Boot REST Resource not showing linked objects (sets)

I've a database and some classes. These classes are linked with OneToMany, and so on.
If I print the object itself with spring it contains everything. But if I print it with the Resource feature, it contains only the variables, which are no collections or linked otherwise with an other class.
How can I add the collections to the output?
By default Spring Data REST does not show associated resources except as links. If you want that you have to define projections that describe the fields you want to see, whether they're simple fields like the ones you describe or associated resources. See
http://docs.spring.io/spring-data/rest/docs/current/reference/html/#projections-excerpts
For example say you have a Service resource with associations to resources like serviceType, serviceGroup, owner, serviceInstances and docLinks. If you want those to show up in the response body you can create a projection:
package my.app.entity.projection;
import org.springframework.data.rest.core.config.Projection;
...
#Projection(name = "serviceDetails", types = Service.class)
public interface ServiceDetails {
String getKey();
String getName();
ServiceType getType();
ServiceGroup getGroup();
Person getOwner();
List<ServiceInstance> getServiceInstances();
List<DocLink> getDocLinks();
String getPlatform();
}
Then GET your URL with the projection:
http://localhost:8080/api/services/15?projection=serviceDetails
The result will include the projected properties:
{
"name" : "MegaphoneService",
"key" : "megaphone",
"type" : {
"key" : "application",
"name" : "User Application",
"description" : "A service that allows users to use a megaphone."
},
"owner" : null,
"serviceInstances" : [ {
"key" : "megaphone-a-dr",
"description" : null,
"loadBalanced" : true,
"minCapacityDeploy" : null,
"minCapacityOps" : 50
}, ... ],
...
}

Jackson: can json specify the target type?

This is what my class looks like -
public class A {
private Map<String, Object> objects = null;
....
}
My json would be like -
{
"f1" : {
"name" : "some name",
"val" : 3
},
"f2" : {
"arg": {
some field/value pairs
}
}
}
What I want is to specify in the JSON itself the type to which it can be deserialized to. So the value for f1 would be converted to an object of class B and f2 would get converted to object of C.
My code will look like this -
Object o = objects.get("f1");
if (o instanceof B) {
...
} else if (o instanceof C) {
...
}
Is there a way to do this? I want the json to control the deserialization.
Yes, Jackson can use a type identifier if JSON document has it. This is usually done by using annotation #JsonTypeInfo.
There are multiple ways to add/use type identifier, both regarding how it is included in JSON document, and as to what kind of id is being used (type name or Java class name?).
The easiest way to see how things match is to actually start with a POJO, add #JsonTypeInfo annotation, and serialize it to see kind of JSON produced. And once you understood how inclusion works you can modify, if necessary, structure of JSON and/or Java class definition.

Repetitive string replacement

I have a string created from a JSON object as follows
{
"id" : "112233",
"someElement" : [ {
"map" : {
"123" : [ {..}]
},
"map" : {
"124" :[ {..}]
}
}]
}
I need to convert the above string to the following format.
{
"id" : "112233",
"someElement" : [ {
"123" : {
"element" : [ {..}]
},
"124" : {
"element" :[ {..}]
}
}]
}
I tried to do string substitution as when the substring "map" is found in the string, replace with the ID just beneath it.
String a = jsonString.substring(jsonString.indexOf("map")+16, jsonString.indexOf("map")+19);
String b = jsonString.replace("map", a);
This pattern works for the first occurrence of "map" string. But the same ID value replaces the second "map" string. How do I replace the subsequent occurrences of "map" string with their respective IDs.
Also, is there any better way to do this? Appreciate any feedback.Thanks!
JSON is not a regular language, so trying to make this kind of change with a regular expression will be fragile; syntactically insignificant variations in the input will easily confuse your regular-expression–based solution.
Because this example violates the JSON recommendation for keeping object member names unique, many JSON parsers will have difficulty parsing it, raising an exception or ignoring some members. However, there might be parsers out there that handle it. If not, it's very easy write your own parser for JSON that will handle this input robustly. Then your code won't break when the whitespace changes.

Configure XStream to dynamically map to different objects

I am hitting a RESTful 3rd party API that always sends JSON in the following format:
{
"response": {
...
}
}
Where ... is the response object that needs to be mapped back to a Java POJO. For instance, sometimes the JSON will contain data that should be mapped back to a Fruit POJO:
{
"response": {
"type": "orange",
"shape": "round"
}
}
...and sometimes the JSON will contain data that should be mapped back to an Employee POJO:
{
"response": {
"name": "John Smith",
"employee_ID": "12345",
"isSupervisor": "true",
"jobTitle": "Chief Burninator"
}
}
So depending on the RESTful API call, we need these two JSON results mapped back to one of the two:
public class Fruit {
private String type;
private String shape;
// Getters & setters for all properties
}
public class Employee {
private String name;
private Integer employeeId;
private Boolean isSupervisor;
private String jobTitle;
// Getters & setters for all properties
}
Unfortunately, I cannot change the fact that this 3rd party REST service always sends back a { "response": { ... } } JSON result. But I still need a way to configure a mapper to dynamically map such a response back to either a Fruit or an Employee.
First, I tried Jackson with limited success, but it wasn't as configurable as I wanted it to be. So now I am trying to use XStream with its JettisonMappedXmlDriver for mapping JSON back to POJOs. Here's the prototype code I have:
public static void main(String[] args) {
XStream xs = new XStream(new JettisonMappedXmlDriver());
xs.alias("response", Fruit.class);
xs.alias("response", Employee.class);
// When XStream sees "employee_ID" in the JSON, replace it with
// "employeeID" to match the field on the POJO.
xs.aliasField("employeeID", Employee.class, "employee_ID");
// Hits 3rd party RESTful API and returns the "*fruit version*" of the JSON.
String json = externalService.getFruit();
Fruit fruit = (Fruit)xs.fromXML(json);
}
Unfortunately when I run this I get an exception, because I have xs.alias("response", ...) mapping response to 2 different Java objects:
Caused by: com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$UnknownFieldException: No such field me.myorg.myapp.domain.Employee.type
---- Debugging information ----
field : type
class : me.myorg.myapp.domain.Employee
required-type : me.myorg.myapp.domain.Employee
converter-type : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
path : /response/type
line number : -1
version : null
-------------------------------
So I ask: what can I do to circumvent the fact that the API will always send back the same "wrapper" response JSON object? The only thing I can think of is first doing a String-replace like so:
String json = externalService.getFruit();
json = json.replaceAll("response", "fruit");
...
But this seems like an ugly hack. Does XStream (or another mapping framework) provide anything that would help me out in this particular case? Thansk in advance.
There are two ways with Jackson:
test manually that the wanted keys are there (JsonNode has the necessary methods);
use JSON Schema; there is one API in Java: json-schema-validator (yes, that is mine), which uses Jackson.
Write a schema matching your first object type:
{
"type": "object",
"properties": {
"type": {
"type": "string",
"required": true
},
"shape": {
"type": "string",
"required": true
}
},
"additionalProperties": false
}
Load this as a schema, validate your input against it: if it validates, you know you need to deserialize against your fruit class. Otherwise, make the schema for the second item type, validate against it as a security measure, and deserialize using the other class.
There are code examples for the API, too (version 1.4.x)
If you do know the actual type, it should be relatively straight-forward with Jackson.
You need to use a generic wrapper type like:
public class Wrapper<T> {
public T response;
}
and then the only trick is to construct type object to let Jackson know what T there is.
If it is statically available, you just do:
Wrapper<Fruit> wrapped = mapper.readValue(input, new TypeReference<Wrapper<Fruit>>() { });
Fruit fruit = wrapped.response;
but if it is more dynamically generated, something like:
Class<?> rawType = ... ; // determined using whatever logic is needed
JavaType actualType = mapper.getTypeFactory().constructGenericType(Wrapper.class, rawType);
Wrapper<?> wrapper = mapper.readValue(input, actualType);
Object value = wrapper.response;
but either way it "should just work". Note that in latter case you may be able to use base types ("? extends MyBaseType"), but in general dynamic type can't be specified.

Categories

Resources