Json nested Objects check if null - java

consider the following json
{
"sub1":{
"name":"Name-sub1",
"sub11":{
"name":"Name-sub11",
"sub111":{
"name":"Name-sub111",
...
},
..
},
...
},
...
}
I now want to fetch the inner name Element (Name-sub111) in java (it's a io.vertx.core.json.JsonObject)
object.getJsonObject("sub1").getJsonObject("sub11").getJsonObject("sub111").getString("name")
But I'm not sure if sub1 or sub11 or sub111 even exist - so I always need to check for null
if(object.getJsonObject("sub1") != null && object.getJsonObject("sub1").getJsonObject("sub11") != null && object.getJsonObject("sub1").getJsonObject("sub11").getJsonObject("sub111") != null) {
return object.getJsonObject("sub1").getJsonObject("sub11").getJsonObject("sub111").getString("name");
}
Does someone know a better solutions for this case?

You might want to consider creating some helper methods that return Optional objects.
public static Optional<JsonObject> getJsonObject(JsonObject obj, String prop) {
return Optional.ofNullable(obj.getJsonObject(prop));
}
public static Optional<String> getString(JsonObject obj, String prop) {
return Optional.ofNullable(obj.getString(prop));
}
Then you can compose the methods to achieve the desired result.
try {
return getJsonObject(obj, "sub1").map((obj) -> getJsonObject(obj, "sub2")).map((obj) -> getString(obj, "sub3")).get();
} catch (NoSuchElementException e) {
return null;
}

an option here is to use Java 8 Optional, which eliminates the dupplicate method calls. Howewver, the end result might not look clear enough for some:
return Optional.ofNullable(object.getJsonObject("sub1")).map(subObj1 ->
Optional.ofNullable(subObj1.getJsonObject("sub11")).map(subObj11 ->
Optional.ofNullable(subObj11.getJsonObject("sub111")).map(subObj111 -> subObj111.getString("name")
)));

If it's not one-time parsing thing, you can map your json to java object and use Optional class:
import io.vertx.core.json.JsonObject;
import java.util.Optional;
class Scratch {
public static void main(String[] args) {
String json = "{\n" +
" \"sub1\" : {\n" +
" \"name\" : \"Name-sub1\",\n" +
" \"sub11\" : {\n" +
" \"name\" : \"Name-sub11\",\n" +
" \"sub111\" : {\n" +
" \"name\" : \"Name-sub111\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
JsonObject object = new JsonObject(json);
SamplePojo pojo = object.mapTo(SamplePojo.class);
System.out.println(pojo);
String sub111name = pojo.getSub1()
.flatMap(Sub1::getSub11)
.flatMap(Sub11::getSub111)
.map(Sub111::getName)
.orElse("");
System.out.println(sub111name);
}
}
class SamplePojo {
private Sub1 sub1;
public Optional<Sub1> getSub1() {
return Optional.ofNullable(sub1);
}
public void setSub1(Sub1 sub1) {
this.sub1 = sub1;
}
}
class Sub1 {
private String name;
private Sub11 sub11;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Optional<Sub11> getSub11() {
return Optional.ofNullable(sub11);
}
public void setSub11(Sub11 sub11) {
this.sub11 = sub11;
}
}
class Sub11 {
private String name;
private Sub111 sub111;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Optional<Sub111> getSub111() {
return Optional.ofNullable(sub111);
}
public void setSub111(Sub111 sub111) {
this.sub111 = sub111;
}
}
class Sub111 {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

Simple yet efficient solution, using reduction:
List<String> nodesToTraverse = Arrays.asList("sub1", "sub11", "sub111");
JsonObject leaf = nodesToTraverse.stream().reduce(object,
(obj, node) -> obj == null ? null : obj.getJsonObject(node),
(prev, cur) -> cur);
return leaf != null ? leaf.getString("name") : null;

Might not be the best solution, because this Exception could also be caused for another reason.
try {
object.getJsonObject("sub1").getJsonObject("sub11").getJsonObject("sub111").getString("name")
} catch(NullpointerException e) {
// handle error
}

Related

Removing a map entry causes object reference within Map entry optional to change

When i retrieve a map entry from a map, store it in an optional and then remove that same entry from the map using remove(entry.getKey()) then the Optional suddenly starts pointing to the next map entry available inside the map.
Let me explain further:
I have a bunch of comment objects that i would like to sort. The comment list should always start with the comment that is accepted as the answer, it should be the first element in the list. The sort method starts with a map and uses a stream on entrySet to retrieve the first comment which has the acceptedAnswer boolean set to true.
Map<Long, CommentDTO> sortedAndLinkedCommentDTOMap = sortCommentsAndLinkCommentRepliesWithOwningComments(commentDTOSet);
Optional<Map.Entry<Long, CommentDTO>> acceptedAnswerCommentOptional = sortedAndLinkedCommentDTOMap.entrySet().stream()
.filter(entry -> entry.getValue().isAcceptedAsAnswer()).findFirst();
Lets assume that the Map contains 3 comments with ids 3, 6, and 11. The key is always the id of the comment and the comment is always the value. The comment marked as the answer has id 6. In this case the code below is executed:
if(acceptedAnswerCommentOptional.isPresent()){
Map.Entry<Long, CommentDTO> commentDTOEntry = acceptedAnswerCommentOptional.get();
sortedAndLinkedCommentDTOMap.remove(commentDTOEntry.getKey());
}
When commentDTOEntry gets initialized with the value of acceptedAnswerCommentOptional it has a reference to the accepted answer with id 6. Now, when i remove that entry from sortedAndLinkedCommentDTOMap the reference to the accepted answer comment is not only removed from sortedAndLinkedCommentDTOMap but also from acceptedAnswerCommentOptional! But instead of becoming null acceptedAnswerCommentOptional now starts pointing to the next entry of sortedAndLinkedCommentDTOMap namely the one with key 11.
I don't understand what is causing this strange behavior. Why doesn't the value of acceptedAnswerCommentOptional simply become null? And why isn't acceptedAnswerCommentOptional able to maintain the reference to the accepted answer comment when i remove it from the map?
You can see this behavior yourself when running the code in intellij IDEA using debug mode, as soon as the remove method is called the explanatory debug label for commentDTOEntry next to acceptedAnswerCommentOptional flips from 6 -> .... to 11 -> ....
EDIT: I've made a reproducible example according to WJSs wishes. This is the code:
import java.util.*;
import java.math.BigInteger;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.function.Function;
class CommentDTO implements Comparable<CommentDTO> {
private BigInteger id;
private BigInteger owningCommentId;
private BigInteger commenterId;
private Long owningEntityId;
private String commenterName;
private String commenterRole;
private String country;
private String thumbnailImageUrl;
private String content;
private String commentDateVerbalized;
private boolean flagged;
private Integer flagCount;
private boolean deleted;
private boolean liked;
private Integer likeCount;
private String lastEditedOnVerbalized;
private boolean acceptedAsAnswer;
private boolean rightToLeft;
private TreeSet<CommentDTO> replies = new TreeSet<>();
public CommentDTO() {
}
public CommentDTO(boolean acceptedAsAnswer, BigInteger id){
this.acceptedAsAnswer = acceptedAsAnswer;
this.id = id;
}
public CommentDTO(boolean acceptedAsAnswer, BigInteger id, BigInteger owningCommentId){
this.acceptedAsAnswer = acceptedAsAnswer;
this.id = id;
this.owningCommentId = owningCommentId;
}
public BigInteger getId() {
return id;
}
public void setId(BigInteger id) {
this.id = id;
}
public BigInteger getOwningCommentId() {
return owningCommentId;
}
public void setOwningCommentId(BigInteger owningCommentId) {
this.owningCommentId = owningCommentId;
}
public BigInteger getCommenterId() {
return commenterId;
}
public void setCommenterId(BigInteger commenterId) {
this.commenterId = commenterId;
}
public Long getOwningEntityId() {
return owningEntityId;
}
public void setOwningEntityId(Long owningEntityId) {
this.owningEntityId = owningEntityId;
}
public String getCommenterName() {
return commenterName;
}
public void setCommenterName(String commenterName) {
this.commenterName = commenterName;
}
public String getCommenterRole() {
return commenterRole;
}
public void setCommenterRole(String commenterRole) {
this.commenterRole = commenterRole;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getCommentDateVerbalized() {
return commentDateVerbalized;
}
public void setCommentDateVerbalized(String commentDateVerbalized) {
this.commentDateVerbalized = commentDateVerbalized;
}
public boolean isFlagged() {
return flagged;
}
public void setFlagged(boolean flagged) {
this.flagged = flagged;
}
public Integer getFlagCount() {
return flagCount;
}
public void setFlagCount(Integer flagCount) {
this.flagCount = flagCount;
}
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public boolean isLiked() {
return liked;
}
public void setLiked(boolean liked) {
this.liked = liked;
}
public Integer getLikeCount() {
return likeCount;
}
public void setLikeCount(Integer likeCount) {
this.likeCount = likeCount;
}
public TreeSet<CommentDTO> getReplies() {
return replies;
}
public void setReplies(TreeSet<CommentDTO> replies) {
this.replies = replies;
}
public String getLastEditedOnVerbalized() {
return lastEditedOnVerbalized;
}
public void setLastEditedOnVerbalized(String lastEditedOnVerbalized) {
this.lastEditedOnVerbalized = lastEditedOnVerbalized;
}
public String getThumbnailImageUrl() {
return thumbnailImageUrl;
}
public void setThumbnailImageUrl(String thumbnailImageUrl) {
this.thumbnailImageUrl = thumbnailImageUrl;
}
public boolean isAcceptedAsAnswer() {
return acceptedAsAnswer;
}
public void setAcceptedAsAnswer(boolean acceptedAsAnswer) {
this.acceptedAsAnswer = acceptedAsAnswer;
}
public boolean isRightToLeft() {
return rightToLeft;
}
public void setRightToLeft(boolean rightToLeft) {
this.rightToLeft = rightToLeft;
}
#Override
public int compareTo(CommentDTO o) {
return this.id.compareTo(o.id);
}
#Override
public String toString() {
return "CommentDTO{" +
"id=" + id +
", owningCommentId=" + owningCommentId +
", commenterId=" + commenterId +
", owningEntityId=" + owningEntityId +
", commenterName='" + commenterName + '\'' +
", commenterRole='" + commenterRole + '\'' +
", country='" + country + '\'' +
", thumbnailImageUrl='" + thumbnailImageUrl + '\'' +
", content='" + content + '\'' +
", commentDateVerbalized='" + commentDateVerbalized + '\'' +
", flagged=" + flagged +
", flagCount=" + flagCount +
", deleted=" + deleted +
", liked=" + liked +
", likeCount=" + likeCount +
", lastEditedOnVerbalized='" + lastEditedOnVerbalized + '\'' +
", acceptedAsAnswer=" + acceptedAsAnswer +
", rightToLeft=" + rightToLeft +
", replies=" + replies +
'}';
}
}
public class HelloWorld implements Comparable<HelloWorld> {
private Long id;
private boolean acceptedAsAnswer;
public HelloWorld(){}
public HelloWorld(boolean acceptedAsAnswer, Long id){
this.acceptedAsAnswer = acceptedAsAnswer;
this.id = id;
}
#Override
public String toString() {
return "id= " + id + " acceptedAsAnswer= " + acceptedAsAnswer;
}
public boolean isAcceptedAsAnswer(){
return acceptedAsAnswer;
}
public long getId(){
return id;
}
public static void main(String []args){
HelloWorld helloWorld = new HelloWorld();
helloWorld.doTest();
}
#Override
public int compareTo(HelloWorld o) {
return this.id.compareTo(o.id);
}
public void doTest(){
Set<CommentDTO> commentDTOSet = new HashSet<>();
commentDTOSet.add( new CommentDTO(false, BigInteger.valueOf(3)));
commentDTOSet.add( new CommentDTO(true, BigInteger.valueOf(6)));
commentDTOSet.add( new CommentDTO(false, BigInteger.valueOf(11)));
commentDTOSet.add( new CommentDTO(true, BigInteger.valueOf(7), BigInteger.valueOf(6)));
commentDTOSet.add( new CommentDTO(true, BigInteger.valueOf(8), BigInteger.valueOf(6)));
Map<Long, CommentDTO> sortedAndLinkedCommentDTOMap = sortCommentsAndLinkCommentRepliesWithOwningComments(commentDTOSet);
Optional<Map.Entry<Long, CommentDTO>> acceptedAnswerCommentOptional = sortedAndLinkedCommentDTOMap.entrySet().stream()
.filter(entry -> entry.getValue().isAcceptedAsAnswer()).findFirst();
if(acceptedAnswerCommentOptional.isPresent()){
Map.Entry<Long, CommentDTO> commentDTOEntry = acceptedAnswerCommentOptional.get();
System.out.println(commentDTOEntry.toString());
sortedAndLinkedCommentDTOMap.remove(commentDTOEntry.getKey());
System.out.println(commentDTOEntry.toString());
}
}
private Map<Long, CommentDTO> sortCommentsAndLinkCommentRepliesWithOwningComments(Set<CommentDTO> commentDTOSet){
Map<Long, CommentDTO> commentDTOMap = commentDTOSet.stream()
.collect(Collectors.toMap(comment -> comment.getId().longValueExact(), Function.identity(), (v1,v2) -> v1, TreeMap::new));
commentDTOSet.forEach(commentDTO -> {
BigInteger owningCommentId = commentDTO.getOwningCommentId();
if(owningCommentId != null){
CommentDTO owningCommentDTO = commentDTOMap.get(owningCommentId.longValueExact());
owningCommentDTO.getReplies().add(commentDTO);
}
});
commentDTOMap.values().removeIf(commentDTO -> commentDTO.getOwningCommentId() != null);
return commentDTOMap;
}
}
You can run the code above here: https://www.tutorialspoint.com/compile_java_online.php
EDIT 2: example code reproduces my problem now.
EDIT 3:
This line of code
commentDTOMap.values().removeIf(commentDTO -> commentDTO.getOwningCommentId() != null);
is causing the observed behavior. The accepted answer (commentDTO with id 6) has 2 replies to it. Those 2 comments (with id 7 and 8) are 'owned' by CommentDTO 6 and are also referenced by the replies list inside CommentDTO 6. At the end of sortCommentsAndLinkCommentRepliesWithOwningComments() I remove all CommentDTOs that can be considered replies to another comment owningCommentId != null. I do this because these comments are now referenced from within the replies lists of the owning comments. If i were to leave them in the original map then those replies will appear twice. Therefore i remove them but this is causing unexpected behavior. I would like to know why this is so.
That happens because the map you are using is a TreeMap.
A TreeMap is implemented as a red-black tree, which is a self-balancing binary tree.
The entries of the map are used as the nodes of the tree.
If you remove one entry then the tree has to re-balance itself and it might happen that the entry is then used to point to the node that takes its place.
Since TreeMap.entrySet() is backed by the map, the changes are reflected in the set.
The changes depends also on which node you are removing, for example if it's a leaf then it might just be unlinked from the tree and the entry is left unaffected.
If you use another map implementation like an HashMap then you won't get this behavior.
By the way, here's a simpler example, which doesn't even involve Optional or custom classes:
Map<Long, String> map = new TreeMap<>();
map.put(1L, "a");
map.put(2L, "b");
map.put(3L, "c");
map.put(4L, "d");
map.put(5L, "e");
map.put(6L, "f");
Map.Entry<Long, String> entry = map.entrySet().stream()
.filter(e -> e.getKey().equals(4L))
.findFirst()
.get();
System.out.println(entry); // prints 4=d
map.remove(entry.getKey());
System.out.println(entry); // prints 5=e

JAVA : How make custom inner class for complex Json

I've read a lot posts but don't find how make the class for this json.
{
"snippet": {
"parentGroupId": "69ea5920-0157-1000-0000-0000028e1b90",
"processors": {},
"processGroups": {
"1231-23a": {
"clientId": "50b3ec1a-c123-1e4f-718c-b0323fb1e175",
"version": 0
}
}
}
}
And the problem is that property "1231-23a" can change in my json like this :
{
"snippet": {
"parentGroupId": "69ea5920-0157-1000-0000-0000028e1b90",
"processors": {},
"processGroups": {
"4544-412f": {
"clientId": "50b3ec1a-c123-1e4f-718c-b0323fb1e175",
"version": 0
}
}
}
}
thanks for yours helps
You can use a Map from String to the nested data for example ProcessGroup. Then "1231-23a" and "4544-412f" would be keys in this Map. For example these classes for "snippet" and "processGroups" and add constructors, getters etc
class Snippet {
String parentGroupId;
Processors processors;
Map<String, ProcessGroup> processGroups;
}
class ProcessGroup {
String clientId;
int version;
}
#manos,
It's missing in my classes to work correctly.
snippet
class Snippet {
String parentGroupId;
// Processors processors;
Map<String, ProcessGroup> processGroups;
public void setParentGroupId(String string) {
this.parentGroupId = string ;
}
public void setProcessGroups(Map<String, ProcessGroup> procGps)
{
this.processGroups = procGps;
}
public String toString()
{
String str = "{\n"
+ " \"snippet\": {\n"
+" \"parentGroupId\": \""+ this.parentGroupId +"\",\n"
+" \"processGroups\": \""+ this.processGroups +"\""
+ "\n}";
return str;
}
}
ProcessGroup class
class ProcessGroup {
String clientId;
int version;
public void setClientId(String clientId) {
this.clientId = clientId;
}
public void setVersion(int version) {
this.version = version;
}
public String toString()
{
String str = "\n \"clientId\": \"" + this.clientId +"\"\n "
+" \"version\": "+ this.version;
return str;
}
}
Test class
public class Test {
public static void main(String[] args) {
Map<String, ProcessGroup> map = new HashMap<String, ProcessGroup>();
// ObjectMapper oMapper = new ObjectMapper();
//Student obj = new Student();
Snippet obj = new Snippet();
ProcessGroup objPG = new ProcessGroup();
objPG.setClientId("clientId-10");
objPG.setVersion(12);
obj.setParentGroupId("parentId-10");
map.put("processGroup-10", objPG);
obj.setProcessGroups(map);
// object -> Map
System.out.println(obj);
}
}
result
it seems, i don't have 'access' to processGroup-10 and plus the sign "=" and not ":"
What's wrong ?
{
"snippet": {
"parentGroupId": "parentId-10",
"processGroups": "{processGroup-10=
"clientId": "clientId-10"
"version": 12}"
}

Create map of maps from eclipse toString()

Eclipse can auto-generate a toString() method from a object's fields. If those fields are objects then they too may have similarly auto-generated toString() methods.
e.g. a President object might look like this:
President [country=USA, name=Name [title=Mr, forename=Barack, surname=Obama], address=Address [houseNumber=1600, street=Pennsylvania Avenue, town=Washington]]
which is easier to read if I format it:
President [
country=USA,
name=Name [
title=Mr,
forename=Barack,
surname=Obama],
address=Address [
houseNumber=1600,
street=Pennsylvania Avenue,
town=Washington]]
What is the best way to parse this String to create a map of maps?
I've got a solution, but it's not pretty. I was hoping to be able to avoid the low level String manipulation somehow, but here it is:
import java.util.LinkedHashMap;
import java.util.Map;
public class MappedObject {
public String className;
public Map<String, String> leafFields = new LinkedHashMap<>();
public Map<String, MappedObject> treeFields = new LinkedHashMap<>();
#Override
public String toString() {
return "[className=" + className
+ (leafFields.isEmpty() ? "" : ", leafFields=" + leafFields)
+ (treeFields.isEmpty() ? "" : ", treeFields=" + treeFields)
+ "]";
}
public static MappedObject createFromString(String s) {
MappedObject mo = new MappedObject();
new Mapper(s).mapObject(mo);
return mo;
}
private static class Mapper {
private String s;
public Mapper(String s) {
this.s = s;
}
private String mapObject(MappedObject mo) {
mo.className = removeFirstNCharacters(s.indexOf(' '));
while (s.contains("=")) {
removeLeadingNonLetters();
String key = removeFirstNCharacters(s.indexOf('='));
removeFirstNCharacters(1); // remove the =
String leafValue = getLeafValue();
if (leafValue != null) {
mo.leafFields.put(key, leafValue);
if (s.startsWith("]")) { // that was the last field in the tree
return s;
}
} else {
MappedObject treeField = new MappedObject();
mo.treeFields.put(key, treeField);
s = new Mapper(s).mapObject(treeField);
}
}
return s; // s contains only close brackets - ]
}
private void removeLeadingNonLetters() {
int i = 0;
while (!Character.isLetter(s.charAt(i))) {
i++;
}
removeFirstNCharacters(i);
}
private String removeFirstNCharacters(int n) {
String value = s.substring(0, n);
s = s.substring(value.length());
return value;
}
private String getLeafValue() {
int endIndex = getEndIndex();
if (!s.contains("[") || s.indexOf('[') > endIndex) {
return removeFirstNCharacters(endIndex);
}
return null;
}
/** The end of the value, if it's a leaf field. */
private int getEndIndex() {
if(s.contains(",")) {
return Math.min(s.indexOf(','), s.indexOf(']'));
}
return s.indexOf(']');
}
}
}

What is the correct way to do this?

I know this must be a fundamental design problem because I clearly can't do this. I want to call the ownGrokk, ownTyce, etc methods from another class depending on the value of the integer assigned to OwnedSpirits(int). This in turn fills arrays.
The problem is, I do this multiple times, and doing it from another class it seems like I have to make a new object every time to pass the new int argument, and doing so resets the value of spiritInstance. And, since that resets to zero, the arrays don't fill properly. I try to print out my array values later and I get an "ArrayIndexOutOfBoundsException".
public class OwnedSpirits {
private int spiritTypeInt = 0;
public static int spiritInstance=0;
public static int[] spiritarray = new int[9];
public static String[] spiritName = new String[9];
public static int[] party = new int[3];
public OwnedSpirits(int spiritcall){
if(spiritcall == 1){
ownGrokk();
}
if(spiritcall == 2){
ownRisp();
}
if(spiritcall == 3){
ownTyce();
}
if(spiritcall == 4){
ownDaem();
}
if(spiritcall == 5){
ownCeleste();
}
}
private void ownGrokk(){
spiritName[spiritInstance] = "Grokk";
spiritInstance++;
}
private void ownRisp(){
spiritName[spiritInstance] = "Risp";
spiritInstance++;
}
private void ownDaem(){
spiritName[spiritInstance] = "Daem";
spiritInstance++;
}
private void ownCeleste(){
spiritName[spiritInstance] = "Celeste";
spiritInstance++;
}
private void ownTyce(){
spiritName[spiritInstance] = "Tyce";
spiritInstance++;
}
and this code is in another class, where it attempts to call the methods to fill the array
buttonConfirm.addListener(new ClickListener(){
#Override
public void clicked(InputEvent event, float x, float y) {
if(xcounter==3){
for(x=0; x<3; x++){
if(setdaemtrue == true){
new OwnedSpirits(4);
}
if(setrisptrue == true){
new OwnedSpirits(2);
}
if(setcelestetrue == true){
new OwnedSpirits(5);
}
if(settycetrue == true){
new OwnedSpirits(3);
}
if(setgrokktrue == true){
new OwnedSpirits(1);
}
}
}
}
});
and finally in yet another class:
System.arraycopy(OwnedSpirits.spiritName, 0, partylist, 0, 3);
#Override
public void show() {
System.out.println(partylist[0]);
System.out.println(partylist[1]);
System.out.println(partylist[2]);
spiritlist.setItems(partylist);
table.add(spiritlist);
table.setFillParent(true);
stage.addActor(table);
}
If the last part is confusing, it's because I am using libgdx. the print statements are there just to try to figure out why my list was having an error
I can show you what I would do to handle Spirits, and Parties.
The Spirit class, contains name and current party its assigned to:
package com.stackoverflow.spirit;
public class Spirit {
private String name;
private Party party;
private SpiritType type;
private static int id = 0;
public static enum SpiritType {
Grokk, Risp, Tyce, Daem, Celeste
};
public Spirit(String name, SpiritType type) {
create(name, type);
}
public Spirit(SpiritType type) {
create(null, type);
}
// This is to handle Java inexistance of default parameter values.
private void create(String name, SpiritType type)
{
Spirit.id++;
this.name = (name == null) ? (type.name() + " " + id) : name;
this.type = type;
}
public String getName() {
return name;
}
public Party getParty() {
return party;
}
public SpiritType getType() {
return type;
}
/**
* Used internally by #see Party
* #param party the party this Spirit belongs
*/
public void setParty(Party party) {
this.party = party;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString()
{
return this.name;
}
}
Finally the Party class, contains a set of Spirits, you can add and remove Spirits from the party.
package com.stackoverflow.spirit;
import java.util.HashSet;
public class Party {
private HashSet<Spirit> spirits = new HashSet<Spirit>();
private static int id = 0;
private String name = "Party " + Party.id++;;
public Party() {
}
public Party(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void add(Spirit spirit) {
if (!spirits.contains(spirit)) {
spirits.add(spirit);
if (spirit.getParty() != null) {
//Remove from previous party to update the other party set
spirit.getParty().remove(spirit);
}
spirit.setParty(this);
} else {
// throw new SpiritAlreadyOnParty();
}
}
public void remove(Spirit spirit)
{
if (spirits.contains(spirit))
{
spirit.setParty(null); // You could create a default empty party for "Nature/Neutral" Spirits perhaps :)
spirits.remove(spirit);
}
else {
//throw new SpiritNotInParty();
}
}
public boolean isOnParty(Spirit spirit) {
return spirits.contains(spirit);
}
public ArrayList<Spirit> getSpirits()
{
return new ArrayList<Spirit>(spirits);
}
public int getPartySize() {
return spirits.size();
}
public String getPartyInfo()
{
StringBuilder builder = new StringBuilder();
builder.append("Party:" + this.name + " Size:" + this.spirits.size() + "\n");
for (Spirit s : spirits)
{
builder.append(s.getName() + "\n");
}
return builder.toString();
}
#Override
public String toString()
{
return this.name;
}
}
Here I use the Spirit and Party classes, you could add more functionality, like properties for party strength, magic buffs on the party, etc:
package com.stackoverflow.spirit;
import com.stackoverflow.spirit.Spirit.SpiritType;
public class Main {
public static void main(String[] args) throws java.lang.Exception {
Party griffindor = new Party("Griffindor"), slytherin = new Party(
"Slytherin");
// You can also do for (SpiritType type : SpiritType.values() then
// type.ordinal()
for (int i = 0; i < SpiritType.values().length; i++) {
griffindor.add(new Spirit(SpiritType.values()[i]));
slytherin.add(new Spirit(SpiritType.values()[i]));
}
Spirit mySpirit = new Spirit("NotAHPFan", SpiritType.Celeste);
slytherin.add(mySpirit);
System.out.println("Name of party:" + mySpirit.getParty().getName());
System.out.println("Is on griffindor?:"
+ griffindor.isOnParty(mySpirit));
// What now?
griffindor.add(mySpirit);
System.out.println("Is " + mySpirit.getName() + " on "
+ slytherin.getName() + "?:" + slytherin.isOnParty(mySpirit));
System.out.println(mySpirit.getName() + " is now on "
+ mySpirit.getParty() + "\n");
System.out.println(griffindor.getPartyInfo());
System.out.println(slytherin.getPartyInfo());
}
}
P.D: I'm not a HP fan.

Parse JSON with no specific structure for a field with GSON

I am working on an Android application, using the EmpireAvenue API.
The API uses JSON and I'm using the GSON library to parse the data from the API.
Here is the problem:
I have a JSON structure like this:
{
type: "earnings",
info: {
earnings: 64.09
dividends: 1277.34
gains: 1997.05
expenses: 4895.51
shares_bought: 210
shares_bought_user_count: 2
shares_sold: 0
shares_sold_user_count: 0
},
created: "2011-04-16 11:32:37"
},
{
type: "following",
info: [
{
ticker: "SOLPHE"
full_name: "Rodrigo Bermudez Salazar"
list_name: "My Recommended Buys"
},
{
ticker: "SOLPHE"
full_name: "Rodrigo Bermudez Salazar"
list_name: "My Watch List"
}
],
created: "2011-04-16 11:00:08"
}
As you can see, the structure associated with the info field is different. Sometimes it's an object, sometimes an array. As expected, the GSON library throws errors when parsing.
Do you know how to parse a JSON structure with when a field changes structure ?
Thanks for your help.
The current solution with Gson is a bit involved, requiring implementation of a custom Instance Creator and/or a custom Deserializer. Take a look at http://code.google.com/p/google-gson/issues/detail?id=231 and the release notes on Hierarchical Type Adapters for details. I just posted an example of polymorphic deserialization with Gson in response to Polymorphism with gson.
Gson hopefully will soon have the RuntimeTypeAdapter for simpler polymorphic deserialization. See http://code.google.com/p/google-gson/issues/detail?id=231 for more info.
On the other hand, a Jackson-based solution isn't so bad.
public class Foo
{
static String jsonInput =
"[" +
"{" +
"\"type\":\"earnings\"," +
"\"info\":" +
"{" +
"\"earnings\":64.09," +
"\"dividends\":1277.34," +
"\"gains\":1997.05," +
"\"expenses\":4895.51," +
"\"shares_bought\":210," +
"\"shares_bought_user_count\":2," +
"\"shares_sold\":0," +
"\"shares_sold_user_count\":0" +
"}," +
"\"created\":\"2011-04-16 11:32:37\"" +
"}," +
"{" +
"\"type\":\"following\"," +
"\"info\":" +
"[" +
"{" +
"\"ticker\":\"SOLPHE\"," +
"\"full_name\":\"RodrigoBermudezSalazar\"," +
"\"list_name\":\"MyRecommendedBuys\"" +
"}," +
"{" +
"\"ticker\":\"SOLPHE\"," +
"\"full_name\":\"RodrigoBermudezSalazar\"," +
"\"list_name\":\"MyWatchList\"" +
"}" +
"]," +
"\"created\":\"2011-04-16 11:00:08\"" +
"}" +
"]";
public static void main(String[] args) throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new CamelCaseNamingStrategy());
DateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
mapper.setDateFormat(dataFormat);
Collection<Thing> things = mapper.readValue(jsonInput, new TypeReference<Collection<Thing>>(){});
System.out.println(things);
}
}
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
#JsonSubTypes({#Type(value=Earnings.class, name="earnings"), #Type(value=Following.class, name="following")})
abstract class Thing
{
private Date created;
void setCreated(Date created)
{
this.created = created;
}
#Override
public String toString()
{
return String.format(
"[%1$s: created=%2$s, other attributes:%3$s]",
getClass().getSimpleName(), created, toStringAddenda());
}
abstract String toStringAddenda();
}
class Earnings extends Thing
{
private EarningsInfo info;
void setInfo(EarningsInfo info)
{
this.info = info;
}
#Override
String toStringAddenda()
{
return info.toString();
}
}
class Following extends Thing
{
private Collection<FollowingInfo> info;
void setInfo(Collection<FollowingInfo> info)
{
this.info = info;
}
#Override
String toStringAddenda()
{
return info.toString();
}
}
class FollowingInfo
{
private String ticker;
private String fullName;
private String listName;
void setTicker(String ticker)
{
this.ticker = ticker;
}
void setFullName(String fullName)
{
this.fullName = fullName;
}
void setListName(String listName)
{
this.listName = listName;
}
#Override
public String toString()
{
return String.format(
"[FollowingInfo: ticker=%1$s, fullName=%2$s, listName=%3$s]",
ticker, fullName, listName);
}
}
class EarningsInfo
{
private BigDecimal earnings;
private BigDecimal dividends;
private BigDecimal gains;
private BigDecimal expenses;
private int sharesBought;
private int sharesBoughtUserCount;
private int sharesSold;
private int sharesSoldUserCount;
void setEarnings(BigDecimal earnings)
{
this.earnings = earnings;
}
void setDividends(BigDecimal dividends)
{
this.dividends = dividends;
}
void setGains(BigDecimal gains)
{
this.gains = gains;
}
void setExpenses(BigDecimal expenses)
{
this.expenses = expenses;
}
void setSharesBought(int sharesBought)
{
this.sharesBought = sharesBought;
}
void setSharesBoughtUserCount(int sharesBoughtUserCount)
{
this.sharesBoughtUserCount = sharesBoughtUserCount;
}
void setSharesSold(int sharesSold)
{
this.sharesSold = sharesSold;
}
void setSharesSoldUserCount(int sharesSoldUserCount)
{
this.sharesSoldUserCount = sharesSoldUserCount;
}
#Override
public String toString()
{
return String.format(
"[EarningsInfo: earnings=%1$s, dividends=%2$s, gains=%3$s, expenses=%4$s, sharesBought=%5$s, sharesBoughtUserCount=%6$s, sharesSold=%7$s, sharesSoldUserCount=%8$s]",
earnings, dividends, gains, expenses, sharesBought, sharesBoughtUserCount, sharesSold, sharesSoldUserCount);
}
}
class CamelCaseNamingStrategy extends PropertyNamingStrategy
{
#Override
public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName)
{
return convert(defaultName);
}
#Override
public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName)
{
return convert(defaultName);
}
#Override
public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName)
{
return convert(defaultName);
}
private String convert(String defaultName)
{
char[] nameChars = defaultName.toCharArray();
StringBuilder nameTranslated = new StringBuilder(nameChars.length * 2);
for (char c : nameChars)
{
if (Character.isUpperCase(c))
{
nameTranslated.append("_");
c = Character.toLowerCase(c);
}
nameTranslated.append(c);
}
return nameTranslated.toString();
}
}

Categories

Resources