I'm getting to know json better. But I have some problems with that. I want to create JSON objects from Java classes.
I'm trying Java-objects convert to JSON with jackson.
What I want is that:
{
"id" : "005be2f0",
"attachments":
[
{"id":"Y98-8370"},
{"id":"Y98-8371"},
{"id":"Y98-8372"},
{"filename" : "DummyDoc", "filetype" : "pdf"}
]
}
But what I got with the following classes is that:
{
"id" : "005be2f0",
"attachments" : [ {
"id" :
[
{"id":"Y98-8370"},
{"id":"Y98-8371"},
{"id":"Y98-8372"},
],
"filename" : "DummyDoc",
"filetype" : "pdf"
} ]
}
And this are my classes:
public class Attachment {
#JsonPropertyOrder({ "id", "filename", "filetype" })
public class Attachment {
#JsonProperty("id")
private List<AttachmentID> id = new ArrayList<>();
#JsonProperty("filename")
private String filename;
#JsonProperty("filetype")
private String filetype;
public List<AttachmentID> getId() {
return id;
}
public void setId(List<AttachmentID> id) {
this.id = id;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public String getFiletype() {
return filetype;
}
public void setFiletype(String filetype) {
this.filetype = filetype;
}
}
I need this class for ID of the attachment.
public class AttachmentID {
#JsonProperty("id")
private String id;
public AttachmentID(String attachmentID) {
this.id = attachmentID;
}
public AttachmentID() {
// TODO Auto-generated constructor stub
}
#JsonProperty("id")
public String getAttachmentID() {
return id;
}
#JsonProperty("id")
public void setAttachmentID(String attachmentID) {
this.id = attachmentID;
}
}
And my RecordAttachment class.
#JsonPropertyOrder({ "id", "attachments" })
public class RecordAttachment {
#JsonProperty("id")
private String id;
#JsonProperty("attachments")
private List<Attachment> attachments = null;
#JsonProperty("id")
public String getId() {
return id;
}
#JsonProperty("id")
public void setId(String id) {
this.id = id;
}
#JsonProperty("attachments")
public List<Attachment> getAttachments() {
return attachments;
}
#JsonProperty("attachments")
public void setAttachments(List<Attachment> attachments) {
this.attachments = attachments;
}
}
is that possible?
Does this have to do with JsonNode, ObjectNode etc.?
I would be very happy if someone could help me.
I mean with key the IDs in array.
Solution 1 : Custom serializer
Details : You can tell jackson how to serialize your object. If you want to serialize it always in the same way, you can specify at the class level that it should always use this serializer. If you believe you will need other serializers, you should customize your objectMapper instead:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Attachment.class, new AttachmentSerializer());
The attachment serializer:
public class AttachmentSerializer extends StdSerializer<Attachment> {
public AttachmentSerializer() {
this(null);
}
public AttachmentSerializer(Class<Attachment> t) {
super(t);
}
#Override
public void serialize(
Attachment attachment, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeArrayFieldStart("attachments");
for (AttachmentID attachmentID : attachment.getId()) {
jgen.writeStartObject();
jgen.writeStringField("id", attachmentID.getAttachmentID());
jgen.writeEndObject();
}
jgen.writeStartObject();
jgen.writeStringField("filename", attachment.getFilename());
jgen.writeStringField("filetype", attachment.getFiletype());
jgen.writeEndObject();
jgen.writeEndArray();
jgen.writeEndObject();
}
}
and add :
#JsonSerialize(using = AttachmentSerializer.class)
#JsonPropertyOrder({"id", "filename", "filetype"})
public class Attachment {
I've added custom serializer just on your Attachment class because it is the only one that needs special handling.
Solution 2: create a custom RecordAttachmentDTO with the target fields needed and serialize it instead of RecordAttachment.
public class RecordAttachmentDTO {
private String id;
private List<Map<String, String>> attachments = new ArrayList<>();
public RecordAttachmentDTO(RecordAttachment recordAttachment) {
this.id = recordAttachment.getId();
List<Attachment> attachments = recordAttachment.getAttachments();
attachments.forEach(attachment -> addAttachment(attachment));
}
private void addAttachment(Attachment attachment) {
attachment.getId().forEach(attachmentID -> attachments.add(Collections.singletonMap("id", attachmentID.getAttachmentID())));
Map<String, String> fileMap = new HashMap<>();
fileMap.put("filename", attachment.getFilename());
fileMap.put("fileType", attachment.getFiletype());
attachments.add(fileMap);
}
public List<Map<String, String>> getAttachments() {
return attachments;
}
public void setAttachments(List<Map<String, String>> attachments) {
this.attachments = attachments;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
This solution will help you have multiple serializers if needed. This solution is basically a wrapper of your original object. Instead of serializing the original object, you are serializing a wrapper instead.
Solution 3: build JsonNode objects
public static String serializeRecordAttachment(RecordAttachment recordAttachment) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode recordNode = objectMapper.createObjectNode();
recordNode.put("id", recordAttachment.getId());
List<Attachment> attachments = recordAttachment.getAttachments();
recordNode.set("attachments",
createAttachmentNode(objectMapper,attachments));
return objectMapper.writeValueAsString(recordNode.toString);
}
private static ArrayNode createAttachmentNode(ObjectMapper objectMapper, List<Attachment> attachments) {
ArrayNode attachmentsNode = objectMapper.createArrayNode();
attachments.forEach(attachment -> addAttachment(objectMapper, attachmentsNode, attachment);
return attachmentsNode;
}
private static void addAttachment(ObjectMapper objectMapper, ArrayNode attachmentsNode, Attachment attachment) {
attachment.getId().forEach(idField -> {
ObjectNode attchIdNode = objectMapper.createObjectNode();
attchIdNode.put("id", idField.getAttachmentID());
attachmentsNode.add(attchIdNode);
});
ObjectNode fileNode = objectMapper.createObjectNode();
fileNode.put("filename", attachment.getFilename());
fileNode.put("filetype", attachment.getFiletype());
attachmentsNode.add(fileNode);
}
This solution is similar with Solution 2, the advantage is that you don't need a wrapper object.
Related
I am using resttemplate to get a json response body, which looks like this :
{
"entities": {
"Q11649": {
"type": "item",
"id": "Q11649",
"sitelinks": {
"enwiki": {
"site": "enwiki",
"title": "Nirvana (Band)",
"badges": []
},
..
The object "Q11649" is based on a url parameter called ids, for example :
https://www.wikidata.org/w/api.php?action=wbgetentities&format=json&props=sitelinks&ids=Q11649
this part at the end ids=Q11649
I only want the title value from under enwiki key, however the ids is dynamic so I can't create a POJO to map it to that id.
How can create a dynamic resttemplate method to extract only the title from this json structure?
I have a Wikidata class that looks like this :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Wikidata {
#JsonProperty("entities")
private Entity entity;
public Wikidata () {
}
public Wikidata(Entity entity) {
this.entity = entity;
}
public Entity getEntities() {
return entity;
}
public void setEntities(Entity entity) {
this.entity = entity;
}
}
After reading a few articles, I realise that a map is probably the best way to go, this is how my entity class looks like :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Entity {
#JsonProperty()
private Map<Object, Object> data;
public Entity () {
data = new HashMap<>();
}
public Map<Object, Object> getData(){
return data;
}
#JsonAnySetter
public void add(String property, String value){
data.put(property, value);
}
}
My Sitelinks class :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Sitelinks {
private Enwiki enwiki;
public Sitelinks () {
}
public Sitelinks(Enwiki enwiki) {
this.enwiki = enwiki;
}
public Enwiki getEnwiki() {
return enwiki;
}
public void setEnwiki(Enwiki enwiki) {
this.enwiki = enwiki;
}
}
And my Enwiki class :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Enwiki {
private String title;
public Enwiki () {
}
public Enwiki(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
How can I use this map to link between the dynamic id and my sitelinks class so that I can get the title value?
{
{
"1234": {
"name": "bob"
}
},
{
"5678": {
"name": "dan"
}
}
}
I have a class representing name (and other fields, I've just made it simple for this question). But the each element is key'd with the id of the person.
I've tried several things including:
class Name {
String Name;
//getter and setter
}
class NameId {
String id;
Name name;
//getter and setters
}
//json is the string containing of the above json
ArrayList<NameId> map = objectMapper.readValue(json, ArrayList.class);
for (Object m : map) {
LinkedHashMap<String, NameId> l = (LinkedHashMap)m;
Map<String, NameId> value = (Map<String, NameId>) l;
//System.out.println(l);
//System.out.println(value);
for (Object key : value.keySet()) {
System.out.println("key: " + key);
System.out.println("obj: " + value.get(key));
NameId nameId = (NameId)value.get(key);
}
}
The problem I have is it doesn't allow that cast to NameId. The error I get is:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to NameId
Any ideas on the best way to parse such a json string like this properly?
Your json is malformed. You need the square brackets around it otherwise it isn't considered a json array. If your json looks like (for example)
[
{
"1234" : {
"name" : "dan"
}
},
{
"5678" : {
"name" : "mike"
}
}
]
you can write a custom deserializer for the object mapper. See the working example below:
public static void main(String... args) throws Exception {
String testJson = "[{ \"1234\" : { \"name\" : \"dan\" } },{ \"5678\" : { \"name\" : \"mike\" } }]";
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(NameId.class, new MyDeserializer());
mapper.registerModule(module);
ArrayList<NameId> map = mapper.readValue(testJson.getBytes(), new TypeReference<List<NameId>>() {
});
for (NameId m : map) {
System.out.println(m.id);
System.out.println(m.name.name);
System.out.println("----");
}
}
#JsonDeserialize(contentUsing = MyDeserializer.class)
static class NameId {
String id;
Name name;
//getter and setters
}
static class Name {
String name;
//getter and setter
}
static class MyDeserializer extends JsonDeserializer<NameId> {
#Override
public NameId deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = p.getCodec().readTree(p);
Map.Entry<String, JsonNode> nodeData = node.fields().next();
String id = nodeData.getKey();
String name = nodeData.getValue().get("name").asText();
Name nameObj = new Name();
nameObj.name = name;
NameId nameIdObj = new NameId();
nameIdObj.name = nameObj;
nameIdObj.id = id;
return nameIdObj;
}
}
try this
Iterator<String> iterator1 =outerObject.keys();
while(iterator1.hasNext())
{
JsonObject innerObject=outerObject.getJsonObject(iterator1.next());
Iterator<String> iterator2=innerObject.keys();
while(iterator2.hasNext()){
String name=innerObject.getString(iterator2.next());
}
}
Your json is not valid. Maybe a little bit different:
{
"1234": {
"name": "bob"
},
"5678": {
"name": "dan"
}
}
And you could model something like:
class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And instead of attemping to use a list, use a map:
Map<Integer, Person> map = new ObjectMapper().readValue(json,
TypeFactory.defaultInstance()
.constructMapType(Map.class, Integer.class, Person.class));
no custom deserializer needed.
first, the json file as #klaimmore suggested (named test.json):
{
"1234": {
"name": "bob"
},
"5678": {
"name": "dan"
}
}
Secondly. here's the 2 separate class files:
#JsonDeserialize
public class Name {
String name;
public Name(String name) {
this.name = name;
}
/**
* #return the name
*/
public String getName() {
return name;
}
/**
* #param name the name to set
*/
public void setName(String name) {
this.name = name;
}
}
and
#JsonDeserialize
public class NameId {
Name name;
String id;
/**
* #return the id
*/
public String getId() {
return id;
}
/**
* #param id the id to set
*/
public void setId(String id) {
this.id = id;
}
/**
* #return the name
*/
public Name getName() {
return name;
}
/**
* #param name the name to set
*/
public void setName(Name name) {
this.name = name;
}
}
and the extra simple json parser class.
public class JsonParser {
/**
* #param args
* #throws IOException
*/
public static void main(String[] args) throws IOException {
String jsonString = new String(Files.readAllBytes(Paths.get("test.json")));
ObjectMapper mapper = new ObjectMapper();
Map<String,NameId> nameIdList = mapper.readValue(jsonString, new TypeReference<Map<String,NameId>>(){});
nameIdList.entrySet().forEach(nameIdEntry -> System.out.println("name id is: " + nameIdEntry.getKey() +
" and name is: " + nameIdEntry.getValue().getName().getName()));
}
}
also. this is pretty much a dupe of How to convert json string to list of java objects. you should read this.
I have a JSON payload I am attempting to deserialize using Jackson that looks like:
{
"id": "12",
"type": "foo",
"created": "2011-03-20T31:12:00+00:00",
"data": {
"object": {
objectId: 1
fizz: "bizz"
}
}
There are 3 different "type" responses possible {foo, bar, foobar} and, depending on which type is in the payload, the "object" node will have different data underneath it (type foo has fizz for example). I am attempting to write some deserializers that will detect the type, and output a class containing the contents of "object". I can basically ignore the id and created fields. The types of classes are as follows:
public interface ObjectType {
String getId()
}
public class Foo implements ObjectType {
String objectId;
String fizz;
String getId() {
return objectId;
}
}
public class Bar implements ObjectType {
String objectId;
String test;
String getId() {
return objectId;
}
}
public class FooBar implements ObjectType {
String objectId;
String something;
String getId() {
return objectId;
}
}
What is the simplest way to do so in Jackson?
One possibility is to write a custom deserializer. A stub implementation could be as follows:
class ContainerDeserializer extends StdDeserializer<Container> {
private static final long serialVersionUID = 8976140492203602443L;
public ContainerDeserializer() {
this(null);
}
public ContainerDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Container deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int id = (Integer) ((IntNode) node.get("id")).numberValue();
String type = node.get("type").asText();
ObjectType obj;
String objId = node.get("obj").get("objectId").asText();
switch (type) {
case "foo":
String fizz = node.get("obj").get("fizz").asText();
obj = new Foo(objId, fizz);
break;
case "bar":
String test = node.get("obj").get("test").asText();
obj = new Bar(objId, test);
break;
default:
// Error handling
obj = null;
}
return new Container(id, type, obj);
}
}
For the implementation I assumed you have the following Container class:
class Container {
private String type;
private int id;
private ObjectType obj;
public Container(int id, String type, ObjectType obj) {
this.id = id;
this.type = type;
this.obj = obj;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public ObjectType getObj() {
return obj;
}
public void setObj(ObjectType obj) {
this.obj = obj;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
I also added getters and setters to your Foo and Bar classes.
Now just register the Deserializer with
ObjectMapper om = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Container.class, new ContainerDeserializer());
om.registerModule(module);
and deserialize using
om.readValue(test, Container.class);
Mapping an enum class in to DynamoDB object is really simple by using Custom Marshall. But how to map a List of Enum?
Enum class
public enum Transport {
SMS,EMAIL,ALL;
}
DynamoDB mapper
public class Campaign{
private List<Transport> transport;
#DynamoDBAttribute(attributeName = "transport")
public List<Transport> getTransport() {
return transport;
}
public void setTransport(List<Transport> transport) {
this.transport = transport;
}
}
DynamoDBMarshaller is deprecated.
Use DynamoDBTypeConverter instead.
Example:
Enum class
public static enum ValidationFailure {
FRAUD, GENERAL_ERROR
}
DynamoDBTable class
#DynamoDBTable(tableName = "receipt")
public class Receipt {
private Long id;
private List<ValidationFailure> validation;
#DynamoDBHashKey(attributeName = "id")
public Long getId() {
return id;
}
#DynamoDBTypeConverted(converter = ValidationConverter.class)
public List<ValidationFailure> getValidation() {
return validation;
}
public void setId(Long id) {
this.id = id;
}
public void setValidation(List<ValidationFailure> validation) {
this.validation = validation;
}
}
Convertor:
public class ValidationConverter implements DynamoDBTypeConverter<List<String>, List<ValidationFailure>> {
#Override
public List<String> convert(List<ValidationFailure> object) {
List<String> result = new ArrayList<String>();
if (object != null) {
object.stream().forEach(e -> result.add(e.name()));
}
return result;
}
#Override
public List<ValidationFailure> unconvert(List<String> object) {
List<ValidationFailure> result = new ArrayList<ValidationFailure>();
if (object != null) {
object.stream().forEach(e -> result.add(ValidationFailure.valueOf(e)));
}
return result;
}
}
It's working for me, I have used the Set
#DynamoDBTyped(DynamoDBMapperFieldModel.DynamoDBAttributeType.SS)
var roles: MutableSet<Employee.Role>? = null
I think the same approach would work for List with DynamoDBAttributeType.L
I found the answer myself. I create a custom marshall like below.
public class TransportMarshaller implements DynamoDBMarshaller<List<Transport>> {
#Override
public String marshall(List<Transport> transports) {
List<String>transportMap=new ArrayList<>();
for(Transport transport:transports){
transportMap.add(transport.name());
}
return transportMap.toString().replaceAll("\\[|\\]", "");//Save as comma separate value for the purpose of easiness to unmarshall
}
#Override
public List<Transport> unmarshall(Class<List<Transport>> aClass, String s) {
List<String>map= Arrays.asList(s.split("\\s*,\\s*")); //split from comma and parse to List
List<Transport>transports=new ArrayList<>();
for (String st:map){
transports.add(Transport.valueOf(st));
}
return transports;
}
}
Listing:
import java.util.List;
public class Listing<T> {
List<Thing<T>> children;
public List<Thing<T>> getChildren() {
return children;
}
public void setChildren(List<Thing<T>> children) {
this.children = children;
}
}
Thing:
public class Thing<T> {
private String type;
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
Link:
public class Link {
private String author;
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
and here's an example of serialization and deserialization...
public static void main(String[] args) throws IOException {
Link link1 = new Link();
link1.setAuthor("JohnDoe");
Link link2 = new Link();
link2.setAuthor("MaryJane");
List<Thing<Link>> things = new ArrayList<Thing<Link>>();
Thing<Link> thing1 = new Thing();
thing1.setData(link1);
thing1.setType("t3");
Thing<Link> thing2 = new Thing();
thing2.setData(link2);
thing2.setType("t3");
things.add(thing1);
things.add(thing2);
Listing<Link> listing = new Listing<Link>();
listing.setChildren(things);
Thing<Listing> thing = new Thing<Listing>();
thing.setType("listing");
thing.setData(listing);
File jsonFile = new File("src/testMap.txt");
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(jsonFile, thing);
//String jsonString = "{\"type\":\"listing\",\"data\":{\"children\":[{\"type\":\"t3\",\"data\":{\"author\":\"JohnDoe\"}},{\"type\":\"t3\",\"data\":{\"author\":\"MaryJane\"}}]}}";
JavaType jsonType = mapper.getTypeFactory().constructParametricType(Thing.class, Listing.class);
Thing<Listing> readThing = mapper.readValue(jsonFile, jsonType);
}
The problem that I'm having is that the Things contained in the Listing in the sample code above are not parametrized with Link, so their data field is returned as an Object (which is actually LinkedHashMap).
I want to be able to do something like this:
List<Thing<Link>> readListingChildren = readThing.getData().getChildren();
String author = readListingChildren.get(0).getData().getAuthor();
My question is, how would I get this to work using Jackson json?
Note: there will be multiple different types of objects contained by Things, and a Thing's data member's type is defined (or should be defined) by the data object's "type" field, using strings such as t1, t2, t3, etc. which map to different classes.
To achieve a serialized String like
{
"data":{
"type":"listing",
"children":[
{
"data":{
"type":"t3",
"author":"JohnDoe"
}
},
{
"data":{
"type":"t3",
"author":"MaryJane"
}
}
]
}
}
and to use the type information to correctly deserialize the concrete class you may use
#JsonTypeName("listing")
public class Listing<T> {
List<Thing<T>> children;
public List<Thing<T>> getChildren() {
return children;
}
public void setChildren(final List<Thing<T>> children) {
this.children = children;
}
}
public class Thing<T> {
private T data;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(Link.class),
#JsonSubTypes.Type(Listing.class)
})
public T getData() {
return data;
}
public void setData(final T data) {
this.data = data;
}
}
#JsonTypeName("t3")
public class Link {
private String author;
public String getAuthor() {
return author;
}
public void setAuthor(final String author) {
this.author = author;
}
}