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();
}
}
Related
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
}
I am trying to design my class using generics. The following code compiles and works well:
public class Main {
static class TestDataProvider {
public String getString(){
return "Data provider. Test String";
}
public int getInteger() {
return 1510872341;
}
public double getReal() {
return 34.6729;
}
}
interface DataGetterType <T> {
T getData();
}
static class Column <T> {
public String getName() {
return nameTag;
}
public String getDataType(){
return dataTypeTag;
}
public T getData(){
return getter.getData();
}
public Column(String name, String type, DataGetterType<T> getterObj){
nameTag = name;
dataTypeTag = type;
getter = getterObj;
}
private final String nameTag;
private final String dataTypeTag;
private final DataGetterType<T> getter;
}
static class StringColumn extends Column<String> {
StringColumn(String name, TestDataProvider provider) {
super(name, "TEXT", provider::getString);
}
}
static class IntegerColumn extends Column<Integer> {
IntegerColumn(String name, TestDataProvider provider) {
super(name, "INTEGER", provider::getInteger);
}
}
static class RealColumn extends Column<Double> {
RealColumn(String name, TestDataProvider provider) {
super(name, "REAL", provider::getReal);
}
}
public static void main(String[] args) {
TestDataProvider myProvider = new TestDataProvider();
StringColumn modelColumn = new StringColumn("model", myProvider);
IntegerColumn dateColumn = new IntegerColumn("year_of_production", myProvider);
RealColumn weightColumn = new RealColumn("weight", myProvider);
System.out.print(modelColumn.getDataType() + " " + modelColumn.getName() + ": " + modelColumn.getData() + ";\n" +
dateColumn.getDataType() + " " + dateColumn.getName() + ": " + dateColumn.getData() + ";\n" +
weightColumn.getDataType() + " " + weightColumn.getName() + ": " + weightColumn.getData() + ";\n");
}
}
But when I try to make data provider a generic parameter too I get a compile error 'Cannot resolve method 'getString''
static class StringColumn<DataProvider> extends Column<String> {
StringColumn(String name, DataProvider provider) {
super(name, "TEXT", provider::getString); // <- cannot resolve method 'getString'
}
}
I need help with making DataProvider a generic parameter for Column class descendants.
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}"
}
I use Gson to convert JSON data to Java objects. However, the JSON structure has an extra field which could be flattened. Is this possible to do with Gson?
To elaborate (since this is rather difficult to explain), the JSON looks something like this:
{
"foo": "bar",
"data": {
"first": 0,
"second": 1,
"third": 2
}
}
This produces two classes, one for the parent and one for data, like this:
public class Entry {
private String foo;
private Data data;
}
public class Data {
private int first;
private int second;
private int third;
}
I'd like to "flatten" the data field into the parent object so that the Java class would look something like this:
public class Entry {
private String foo;
private int first;
private int second;
private int third;
}
Is this possible with Gson, using e.g. TypeAdapters?
I'll show you demo and you decide for yourself do you really want this... Because it makes TypeAdapter code hard to read.
private static class EntryTypeAdapter extends TypeAdapter<Entry> {
// without registerTypeAdapter(Entry.class, new EntryTypeAdapter())
private Gson gson = new GsonBuilder()
// ignore "foo" from deserialization and serialization
.setExclusionStrategies(new TestExclStrat()).create();
#Override
public void write(JsonWriter out, Entry value) throws IOException {
out.beginObject();
out.name("foo");
out.value(value.foo);
out.name("data");
out.value(gson.toJson(value));
out.endObject();
}
#Override
public Entry read(JsonReader in) throws IOException {
Entry entry = null;
String foo = null;
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
if (name.equals("foo")) {
foo = in.nextString();
} else if (name.equals("data")) {
entry = gson.fromJson(in, Entry.class);
} else {
in.skipValue();
}
}
in.endObject();
if(entry!= null) entry.foo = foo;
return entry;
}
public class TestExclStrat implements ExclusionStrategy {
public boolean shouldSkipClass(Class<?> arg0) {
return false;
}
public boolean shouldSkipField(FieldAttributes f) {
return f.getName().equals("foo");
}
}
}
Can test it with this:
public static void main(String[] args) throws IOException, JSONException {
String jsonString = "{\n" +
" \"foo\": \"bar\",\n" +
" \"data\": {\n" +
" \"first\": 0,\n" +
" \"second\": 1,\n" +
" \"third\": 2\n" +
" }\n" +
"}";
Gson gson = new GsonBuilder()
.registerTypeAdapter(Entry.class, new EntryTypeAdapter()).create();
Entry el = gson.fromJson(jsonString, Entry.class);
String serialized = gson.toJson(el);
System.out.println(serialized);
}
public static class Entry {
public String foo;
public Integer first;
public Integer second;
public Integer third;
}
You could also do something like this:
// even more complicated version without inner Gson help
public Entry readOption2(JsonReader in) throws IOException {
Entry entry = new Entry();
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
if (name.equals("foo")) {
entry.foo = in.nextString();
} else if (name.equals("data")) {
in.beginObject();
while (in.hasNext()) {
name = in.nextName();
if (name.equals("first")) {
entry.first = in.nextInt();
} else if (name.equals("second")) {
entry.second = in.nextInt();
} else if (name.equals("third")) {
entry.third = in.nextInt();
}else{
in.skipValue();
}
}
in.endObject();
} else {
in.skipValue();
}
}
in.endObject();
return entry;
}
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(']');
}
}
}