2

I have this Json content:

{
    "people":[
        {
            "name":"test1",
            "sirname":"test2",
            "details":{
                "social_no":1234567,
                "creadit_card_no":34582342309
            }
        },
        {
            "name":"test3",
            "sirname":"test4",
            "details":{
                "social_no":12345679,
                "creadit_card_no":345823423090
            }
        }
    ]
}

and according to logic this Json should have 3 POJO classes: A class that will hold the list of People, People object and a Details object.

Now my question is, is it possible to deserialize this Json using Jackson or if not possible with Jackson, with GSON library? One that will contain the list of People, and another one, for example Human class, that will have the following structure:

public class Human{

    String name;
    String sirname;
    String social_no;
    String creadit_card_no;
    //..getters and setters
    //should correspond with this json fragment:
      // {
      //  "name":"test1",
      //  "sirname":"test2",
      //  "details":{
      //    "social_no":1234567,
      //    "creadit_card_no":34582342309
      // }
    }
}

So if this is possible, how can I do this?

Update

My actuall json structure is different than the example given here, so here is the original json

So I've created a TypeAdapter on my own, here is the code from this class:

public class PlanTypeAdapter extends TypeAdapter<Plan> {
    private final String TAG = PlanTypeAdapter.class.getSimpleName();

    @Override
    public void write(JsonWriter out, Plan value) throws IOException {
        Log.d(TAG, "WRITE");
    }

    @Override
    public Plan read(JsonReader reader) throws IOException {
        Log.d(TAG, "READ");
        Plan plan = new Plan();
        if (reader.peek() == JsonToken.NULL) {
            reader.nextNull();
            return null;
        }

        reader.setLenient(false);
        while (reader.hasNext()) {
            Log.d(TAG, "PATH: " + reader.getPath());
            Log.d(TAG, "PEEK: " + reader.peek());
            if (reader.peek() == JsonToken.BEGIN_OBJECT) {
                Log.d(TAG, "BEGIN object, path: " + reader.getPath());
                reader.beginObject();
            } else if (reader.peek() == JsonToken.NULL) {
                Log.d(TAG, "NULL");
                reader.skipValue();
            } else if (reader.peek() == JsonToken.END_ARRAY) {
                Log.d(TAG, "END ARRAY");
                if (reader.getPath().contains("retailer")) {
                    reader.endObject();
                } else {
                    reader.endArray();
                }
            } else if (reader.peek() == JsonToken.END_OBJECT) {
                reader.endObject();
                Log.d(TAG, "END object, path: " + reader.getPath());
            } else if (reader.peek() == JsonToken.NUMBER) {
                Log.d(TAG, "NUMBER " + reader.getPath());
            } else if (reader.peek() == JsonToken.BOOLEAN) {
                Log.d(TAG, "BOOLEAN " + reader.getPath());
            } else if (reader.peek() == JsonToken.NAME) {
                switch (reader.nextName()) {
                    case "retailer":
                        reader.beginObject();
                        Log.d(TAG, "RET");
                        break;
                    case "national_plan":
                        reader.beginObject();
                        Log.d(TAG, "NPlan");
                        break;
                    case "name":
                        if (reader.getPath().contains("retailer")) {
                            plan.setRetailer_name(reader.nextString());
                            reader.skipValue();
                            reader.skipValue();
                            reader.endObject();
                        } else {
                            reader.skipValue();
                        }
                        break;
                    case "contract_end":
                        plan.setContract_end(reader.nextString());
                        break;
                    case "data_level_gb":
                        plan.setData_level_gb(reader.nextString());
                        break;
                    case "data_level_id":
                        plan.setData_level_id(reader.nextInt());
                        break;
                    case "days_to_end":
                        plan.setDays_to_switch(reader.nextInt());
                        break;
                    case "direct_from_operator":
                        plan.setDirect_from_operator(reader.nextBoolean());
                        break;
                    case "calculation_amount":
                        plan.setCalculationAmount(reader.nextDouble());
                        break;
                    case "network_generation_name":
                        plan.setNetwork_generation_(reader.nextString());
                        break;
                    case "partner_plan_id":
                        plan.setPartner_plan_id(reader.nextString());
                        break;
                    case "payment_level":
                        plan.setPayment_level(reader.nextString());
                        break;
                    case "payment_level_id":
                        plan.setPayment_level_id(reader.nextInt());
                        break;
                    case "roaming_amount":
                        plan.setRoaming_amount(reader.nextDouble());
                        break;
                    case "savings_amount":
                        plan.setSavings_amount(reader.nextDouble());
                        break;
                    case "savings_avg":
                        plan.setSavings_avg(reader.nextDouble());
                        break;
                    case "savings_percents":
                        plan.setSavings_percents(reader.nextInt());
                        break;
                    default:
                        Log.d(TAG, "DEFAULT " + reader.peek() + "");
                        reader.skipValue();
                        break;
                }

            } else {
                reader.skipValue();
            }
        }

        return plan;
    }
}
Darko Petkovski
  • 3,892
  • 13
  • 53
  • 117

3 Answers3

1

There are different ways you can parse json using the gson library. I will give you two examples.

Method 1 - Write a custom deserializer. This technique uses a class to deserialize the person object. The custom deserializer allows you to create any object you want with the json data. Here are the classes needed to do this:

Group.java:

public class Group {
    @SerializedName("people")
    private List<Person> persons;

    public List<Person> getPersons() {
        return persons;
    }

    public void setPersons(List<Person> persons) {
        this.persons = persons;
    }

    @Override
    public String toString() {
        String NEW_LINE = System.getProperty("line.separator");

        StringBuilder sb = new StringBuilder(this.getClass().getName());
        sb.append("{");
        sb.append(NEW_LINE);

        for(Person p : persons){
            sb.append(p.toString());
        }

        sb.append("}");
        return sb.toString();
    }
}

GsonTest.java:

public class GsonTest {

    public static void main(String[] args) {
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(Person.class, new PersonDeserializer());
        Gson gson = gsonBuilder.create();

        try {
            JsonParser parser = new JsonParser();
            Object obj = parser.parse(new FileReader("C://data.json"));
            JsonObject jsonObject = (JsonObject) obj;

            Group group = gson.fromJson(jsonObject, Group.class);
            System.out.println(group.toString());
        } catch (JsonIOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (JsonSyntaxException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

Person.java:

public class Person {

    public Person(String name, String sirname, Long social_no, Long creadit_card_no) {
        this.name = name;
        this.sirname = sirname;
        this.social_no = social_no;
        this.creadit_card_no = creadit_card_no;
    }

    private String name;
    private String sirname;
    private Long social_no;
    private Long creadit_card_no;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSirname() {
        return sirname;
    }

    public void setSirname(String sirname) {
        this.sirname = sirname;
    }

    public Long getSocial_no() {
        return social_no;
    }

    public void setSocial_no(Long social_no) {
        this.social_no = social_no;
    }

    public Long getCreadit_card_no() {
        return creadit_card_no;
    }

    public void Long(Long creadit_card_no) {
        this.creadit_card_no = creadit_card_no;
    }

    @Override
    public String toString() {
        String NEW_LINE = System.getProperty("line.separator");

        StringBuilder sb = new StringBuilder(this.getClass().getName());
        sb.append("{");
        sb.append(NEW_LINE);
        sb.append("name: ");
        sb.append(name);
        sb.append(NEW_LINE);

        sb.append("sirname: ");
        sb.append(sirname);
        sb.append(NEW_LINE);

        sb.append("social_no: ");
        sb.append(social_no);
        sb.append(NEW_LINE);

        sb.append("creadit_card_no: ");
        sb.append(creadit_card_no);
        sb.append(NEW_LINE);

        sb.append("}");
        sb.append(NEW_LINE);

        return sb.toString();
    }
}

PersonDeserializer.java

public class PersonDeserializer implements JsonDeserializer<Person> {

    public Person deserialize(JsonElement json, Type typeOfT,
        JsonDeserializationContext context) throws JsonParseException {

        JsonObject jsonObject = json.getAsJsonObject();

        String name = jsonObject.get("name").getAsString();
        String sirname = jsonObject.get("sirname").getAsString();

        JsonObject details = jsonObject.get("details").getAsJsonObject();

        Long social_no = details.get("social_no").getAsLong();
        Long creadit_card_no = details.get("creadit_card_no").getAsLong();

        Person person = new Person(name, sirname, social_no, creadit_card_no );

        return person;
    }
}

Method 2 - Use the JsonReader class to parse the json data. You do not have to load the entire json file at once with this technique. This is a better way to parse a large amount of data on devices that have limited resources. This code will be harder to maintain if the json structure changes though. My example code was inspired by this article http://developer.android.com/reference/android/util/JsonReader.html. Use the Person class above with this new GsonTest class:

public class GsonTest {
    List<Person> people = null;

    public GsonTest() {
        people = new ArrayList<Person>();
    }

    public static void main(String[] args) {
        GsonTest gt = new GsonTest();

        gt.doGson();
    }

    void doGson() {
        try {
            InputStream is = GsonTest.class.getResourceAsStream("data.json");

            JsonReader jsonReader = new JsonReader(new InputStreamReader(is, "UTF-8"));

            jsonReader.beginObject();

            while (jsonReader.hasNext()) {
                String name = jsonReader.nextName();
                if (name.equals("people")) {
                    readPeopleArray(jsonReader);
                }
            }

            jsonReader.endObject();

            for(Person p : people){
                System.out.println(p.toString());
            }
        }
        catch (NullPointerException e){
            e.printStackTrace();
        }
        catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private void readPeopleArray(JsonReader jsonReader) throws IOException {
        jsonReader.beginArray();
        while (jsonReader.hasNext()) {
            readPersonObject(jsonReader);
        }
        jsonReader.endArray();
    }

    private void readPersonObject(JsonReader jsonReader) throws IOException {
        String name = null;
        String sirname = null;
        Long social_no = null;
        Long creadit_card_no = null;

        jsonReader.beginObject();
        while(jsonReader.hasNext()){
            String key = jsonReader.nextName();

            if(key.equals("details")){

                jsonReader.beginObject();

                while(jsonReader.hasNext()){
                    String detailKey = jsonReader.nextName();

                    if(detailKey.equals("social_no")){
                        social_no = jsonReader.nextLong();
                    }
                    else if(detailKey.equals("creadit_card_no")){
                        creadit_card_no = jsonReader.nextLong();
                    }
                    else{
                        jsonReader.skipValue();
                    }
                }

                jsonReader.endObject();
            }
            else if(key.equals("name")){
                name = jsonReader.nextString();
            }
            else if(key.equals("sirname")){
                sirname = jsonReader.nextString();
            }
        }
        jsonReader.endObject();

        people.add(new Person(name, sirname, social_no, creadit_card_no));
    }
}
mkk
  • 36
  • 8
  • Thanks for your answer, but I can do this, the issue here is that i need to create object like described in the question - not pure json parsing – Darko Petkovski Jul 31 '15 at 19:07
  • Sorry i misread your question. Check out the changes I made to the Person class. I used the setDetails method to set the values of social_no and creadit_card_no. I believe this creates the object you need. – mkk Jul 31 '15 at 19:22
  • Ah you again are creating the Details object. The thing is that the real json file that im dealing with is very large, so I cant create all objects that are in it. I need a way to create custom objects from the json with only the data I need. – Darko Petkovski Jul 31 '15 at 19:28
  • Can you use a different json parser? I would suggest trying Gson. You could write a deserializer that maps the data you need to your Person class without creating Details objects. Here is an article for reference: http://www.javacreed.com/gson-deserialiser-example/ – mkk Jul 31 '15 at 19:43
1

It seems that at the moment Jackson does not support out of the box such feature to map a field from a nested path. There is an open issue asking for such feature, but it's a question when will it be done. The opposite, serializing a nested object to the first level properties in json, is possible by using a @JsonUnwrapped annotation.

So, in order to overcome the problem, it seems that the only way is to write a custom deserializer, which you could map to your class, and use it to create an instance of the class as you need it.

Stanislav
  • 110
  • 10
  • if so is this possible to do with GSON? – Darko Petkovski Aug 05 '15 at 18:56
  • 1
    It seems that there is also no out of the box solution for that. You would either have to manually move through the JSON node tree and extract the values you need. For sample, please have a look at the http://stackoverflow.com/questions/8233542/parse-a-nested-json-using-gson. Or you would have to write a custom TypeAdapter. You could have a look at the code sample given in http://stackoverflow.com/questions/28054985/flatten-nested-object-into-target-object-with-gson for ideas how to do it. – Stanislav Aug 05 '15 at 22:29
1

If you have a very, very large file, I recommend doing this with a custom deserializer using Gson, but I would not use the JsonDeserializer interface; use the TypeAdapter interface as it is more performant (source). I think that @codemonkey has a very good answer, but it is overly complicated and it can be done much more simply. Specifically, you should never be builidng these Strings yourself (with sb.append()) and you should stay away from JsonDeserializer.

First, create your custom TypeAdapter

public class PersonTypeAdapter extends TypeAdapter<Person> {
  @Override
  public void write(JsonWriter out, Person value) throws IOException {
    if (value == null) {
      out.nullValue();
      return;
    }

    out.beginObject();
    out.name("name").value(value.name);
    out.name("sirname").value(value.sirname);
    out.name("details");
    out.beginObject();
    out.name("social_no").value(value.social_no);
    out.name("creadit_card_no").value(value.creadit_card_no);
    out.endObject();
    out.endObject();
  }

  @Override
  public Person read(JsonReader reader) throws IOException {
    if (reader.peek() == JsonToken.NULL) {
      reader.nextNull();
      return null;
    }

    reader.beginObject();
    validateName(reader, "name");
    String name = reader.nextString();
    validateName(reader, "sirname");
    String sirname = reader.nextString();
    validateName(reader, "details");
    reader.beginObject();
    validateName(reader, "social_no");
    String social_no = reader.nextString();
    validateName(reader, "creadit_card_no");
    String creadit_card_no = reader.nextString();
    reader.endObject();
    reader.endObject();
    return new Person(name, sirname, social_no, creadit_card_no);
  }

  private void validateName(JsonReader reader, String string) throws IOException {
    String name = reader.nextName();
    if(!string.equals(name)) {
      throw new JsonSyntaxException("Expected: \"" + string + "\", got \"" + name + "\"");
    }
  }
}

And, your POJO, obviously:

public class Person {
  public final String name;
  public final String sirname;
  public final String social_no;
  public final String creadit_card_no;

  public Person(String name, String sirname, String social_no,
      String creadit_card_no) {
    this.name = name;
    this.sirname = sirname;
    this.social_no = social_no;
    this.creadit_card_no = creadit_card_no;
  }

  @Override
  public String toString() {
    return String.format(
        "Person [name=%s, sirname=%s, social_no=%s, creadit_card_no=%s]", name,
        sirname, social_no, creadit_card_no);
  }
}

Then, you can parse the Json from your file using the method here. /test.json is just the example you gave in your question.

import java.io.InputStreamReader;
import java.io.Reader;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;

public class PersonExample {
  public static void main(String... args) {
    InputStreamReader streamReader = new InputStreamReader(
        PersonExample.class.getResourceAsStream("/test.json"));

    PeopleWrapper wrapper = parseJSON(streamReader);
    System.out.println(wrapper.people);
  }

  public static class PeopleWrapper {
    @SerializedName("people")
    public List<Person> people;
  }

  public static PeopleWrapper parseJSON(Reader jsonInput) {
    GsonBuilder builder = new GsonBuilder();
    builder.registerTypeAdapter(Person.class, new PersonTypeAdapter());
    Gson gson = builder.create();

    PeopleWrapper peopleWrapper = gson.fromJson(jsonInput, PeopleWrapper.class);

    return peopleWrapper;
  }
}

This program outputs:

[Person [name=test1, sirname=test2, social_no=1234567, creadit_card_no=34582342309], Person [name=test3, sirname=test4, social_no=12345679, creadit_card_no=345823423090]]

So your actual problem is much more complicated than the one you originally described. I will show you a skeleton of the TypeAdapter you need, and you can figure out the rest. Basically, create the Plan object as you've done, and then for each of the outer JSON keys, handle the value.

  • If it's one line, you can just handle it in the switch statement.
  • If it's an array or an object, create a helper method to parse that section of the JSON.

You should assume the JSON is well formed and, if it's not, let Gson throw an Exception. Just tell it to expect what's going to come next.

Here's some code to show you the idea:

import java.io.IOException;

import com.google.gson.*;
import com.google.gson.stream.*;

public class PlanTypeAdapter extends TypeAdapter<Plan> {
    private final String TAG = PlanTypeAdapter.class.getSimpleName();

    @Override
    public void write(JsonWriter out, Plan value) throws IOException {
        Log.d(TAG, "WRITE");
    }

    @Override
    public Plan read(JsonReader reader) throws IOException {
        Log.d(TAG, "READ");
        Plan plan = new Plan();
        if (reader.peek() == JsonToken.NULL) {
            reader.nextNull();
            return null;
        }

        reader.setLenient(false);
        reader.beginObject();

        while (!(reader.peek() == JsonToken.END_OBJECT)) {
            switch (reader.nextName()) {
            case "national_plan":
                handleNationalPlan(reader, plan);
                break;
            case "bill_total":
                handleBillTotal(reader, plan);
                break;
            case "contract_end":
                plan.setContract_end(reader.nextString());
                break;
            case "data_level_gb":
                plan.setData_level_gb(reader.nextString());
                break;
            case "data_level_id":
                plan.setData_level_id(reader.nextInt());
                break;
            case "days_to_end":
                plan.setDays_to_switch(reader.nextInt());
                break;
            case "direct_from_operator":
                plan.setDirect_from_operator(reader.nextBoolean());
                break;
            case "calculation_amount":
                plan.setCalculationAmount(reader.nextDouble());
                break;
            case "network_generation_name":
                plan.setNetwork_generation_(reader.nextString());
                break;
            case "partner_plan_id":
                plan.setPartner_plan_id(reader.nextString());
                break;
            case "payment_level":
                plan.setPayment_level(reader.nextString());
                break;
            case "payment_level_id":
                plan.setPayment_level_id(reader.nextInt());
                break;
            case "roaming_amount":
                plan.setRoaming_amount(reader.nextDouble());
                break;
            case "savings_amount":
                plan.setSavings_amount(reader.nextDouble());
                break;
            case "savings_avg":
                plan.setSavings_avg(reader.nextDouble());
                break;
            case "savings_percents":
                plan.setSavings_percents(reader.nextInt());
                break;
            case "yearly_id":
            case "handset":
            case "internals":
            case "consumer_id":
            case "calculation_details":
            case "operator":
            case "total":
            case "international_plan":
            case "contract_length":
            case "zone":
            case "externals":
            case "cancel_fee":
            case "transformers":
            case "one-offs":
            case "flow":
            case "roaming_plan":
            case "_id":
            // You can use this to ignore the keys you don't care about
            default:
                Log.d(TAG, "DEFAULT " + reader.peek() + "");
                reader.skipValue();
                break;
            }
        }

        reader.endObject();

        return plan;
    }

    private void handleNationalPlan(JsonReader reader, Plan plan) throws IOException {
        reader.beginObject();

        while (!(reader.peek() == JsonToken.END_OBJECT)) {
            switch(reader.nextName()) {
            case "contract_length":
                break;
            case "name":
                break;
            case "country":
            // etc.
            }
        }

        reader.endObject();
    }

    private void handleBillTotal(JsonReader reader, Plan plan) throws IOException {

    }

    // etc.
}
durron597
  • 31,968
  • 17
  • 99
  • 158
  • One question, should I go by the json structure order or I can do a custom getting of the values, example: I can get the `creadit_card_no`, then the `social_no` ? – Darko Petkovski Aug 08 '15 at 08:36
  • 1
    @DarkoPetkovski normally JSON does not guarantee ordering, but it depends on the structure of your particular file. I can modify the answer if you need it to work regardless of order. – durron597 Aug 08 '15 at 11:19
  • First of all I really apreciate that you are helping, and I think this will be the answer of this questions. But im still having some issues while deserializating the json i have. If you can - can you please help me by telling me how can I parse this json file.. its different than the original json example that i posted but it has similar structure. So can you check this file out and modify your answer so I can parse this json file? http://pastebin.com/3FWSs8Bf And I dont need all of the values, i need just some one them. If you are available, can we chat? – Darko Petkovski Aug 08 '15 at 12:03
  • really..? Can u please help me out with that one? – Darko Petkovski Aug 08 '15 at 12:49