Урок 16. Знакомство с форматом JSON. Парсинг JsonObject на примере объекта User – Devcolibri – Android для начинающих

Урок 16. Знакомство с форматом JSON. Парсинг JsonObject на примере объекта User

JSON формат

В этом уроке мы с вами более подробно познакомимся с форматом JSON. А потом из этого формата получим наш User объект.

JSON стал общепринятын форматом для обмена данными в клиент-серверных приложения. Он является универсальным форматом для обмена данными.

Представьте, что вам нужно создать систему приложений, в которую входит, сервер, web-клиент, ios-клиент, android-клиент. У всех технологий разные языки, разные особенности. Поэтому сервер отвечает в формате JSON, а клиенты на своей стороне приводят к нужному формату ответы. Подробнее про JSON формат можно почитать здесь, но на данном этапе ваших знаний уже вполне хватит.

JSONObject, работа с JSON в java коде

Давайте создадим новый класс в пакете network и назовём его JsonParser для преобразования Json-строк в необходимые нам объекты. Добавим в него один метод, который назовём getUser(String response):

 

JsonParser.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package colibri.dev.com.colibritweet.network;

import colibri.dev.com.colibritweet.pojo.User;

public class JsonParser {

    public User getUser(String response) {
        return null;
    }
}

 

Мы будем использовать этот метод внутри класса HttpClient, передавая в него ответ от сервера в формате String, возвращая объект User. Давайте разберём преобразование, рассмотрим структура хранения JSON. В том уроке мы в конце вывели ответ от сервера в log и увидели очень большой объект JSON. На данном этапе нам необходимы только те поля, которые мы отображаем на экране. Поэтому опустим остальные поля и оставим только те, которые нам нужны:

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
    "id": 929257819349700600,
    "name": "Devcolibri",
    "screen_name": "devcolibri",
    "location": "Republic of Belarus",
    "description": "Образовательный IT-портал. Android, Java, Kotlin.",
    "profile_image_url": "http://pbs.twimg.com/profile_images/929258919402377216/JDAtj1H__normal.jpg",
    "followers_count": 5,
    "favourites_count": 0,
}

 

Видим, что наш JSON полностью повторяет структуру нашего объекта User. Теперь можно приступить к парсингу (преобразованию) данных.

Первое, что надо сделать – это создать JSON объект из строки.

 

JsonParser.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package colibri.dev.com.colibritweet.network;

import colibri.dev.com.colibritweet.pojo.User;

public class JsonParser {

    public User getUser(String response) {
        JSONObject userJson = new JSONObject(response);
        return null;
    }
}

 

Видим, что AndroidStudio подчёркивает эту строку, указывая что надо обработать исключение или добавить его к сигнатуре метода. Можем нажать alt + enter и увидеть два этих варианта решения проблемы. Добавим исключение к сигнатуре, чтобы обработать исключения в клиентском коде. Для этого выберем вариант Add exception to method signature.

 

ExceptionSignature.png

 

JSONObject представляет из себя структуру типа HashMap (ключ – значение). Т.е. чтобы получить значения поля id нам необходимо выполнить вызов метода userJson.getLong("id"). Давайте сделаем тоже самое для каждого поля. После этого метод будет выглядеть так:

 

JsonParser.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class JsonParser {

    public User getUser(String response) throws JSONException {
        JSONObject userJson = new JSONObject(response);
        long id = userJson.getLong("id");
        String name = userJson.getString("name");
        String nick = userJson.getString("screen_name");
        String location = userJson.getString("location");
        String description = userJson.getString("description");
        String imageUrl = userJson.getString("profile_image_url");
        int followersCount = userJson.getInt("followers_count");
        int followingCount = userJson.getInt("favourites_count");

        return null;
    }
}

 

Т.е. вот так по названию поля мы можем достать его значение. Нам попался самый простой случай, где наши значения являются примитивными типами. На практике мы поработаем с массивами элементов, вложенными объектами.

Давайте создадим объект User, передав ему в конструктор все поля, которые мы только что достали из userJson.

 

JsonParser.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class JsonParser {

    public User getUser(String response) throws JSONException {
        JSONObject userJson = new JSONObject(response);
        long id = userJson.getLong("id");
        String name = userJson.getString("name");
        String nick = userJson.getString("screen_name");
        String location = userJson.getString("location");
        String description = userJson.getString("description");
        String imageUrl = userJson.getString("profile_image_url");
        int followersCount = userJson.getInt("followers_count");
        int followingCount = userJson.getInt("favourites_count");

        return new User(id, imageUrl, name, nick, description, location, followingCount, followersCount);
    }
}

 

Вот и всё! Теперь давайте добавим использование нашего класса внутри класса HttpClient.

 

HttpClient.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class HttpClient {
    private static final String HEADER_AUTHORIZATION = "Authorization";
    private static final String GET = "GET";
    private final JsonParser jsonParser;

    public HttpClient(){
        jsonParser = new JsonParser();
    }

    public User readUserInfo(long userId) throws IOException, JSONException {
        String requestUrl = "https://api.twitter.com/1.1/users/show.json?user_id=" + userId;

        URL url = new URL(requestUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        // add auth header to request
        String authHeader = getAuthHeader(requestUrl);
        connection.setRequestProperty(HEADER_AUTHORIZATION, authHeader);

        connection.connect();

        InputStream in;
        int status = connection.getResponseCode();
        if (status != HttpURLConnection.HTTP_OK) {
            in = connection.getErrorStream();
        } else {
            in = connection.getInputStream();
        }

        String response = convertStreamToString(in);
        User user = jsonParser.getUser(response);

        return user;
    }
    // остальной код ниже не изменился
}

 

Добавили JsonParser как final поле в классе HttpClient и инициализируем его в конструкторе.

После этого добавили строку User user = jsonParser.getUser(response); в конец метода getUserInfo и поменяли возвращаемый тип на User и добавили новое исключение JSONException в сигнатуру метода. Отлично, теперь изменим код в UserInfoActivity и запустим приложение.

в UserInfoActivity нам нужно изменить метод loadUserInfo. Выглядеть он будет так:

 

UserInfoActivity.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class UserInfoActivity extends AppCompatActivity {
    // остальной код выше не изменился
    @SuppressLint("StaticFieldLeak")
    private void loadUserInfo(final Long userId) {
        new AsyncTask<Void, Void, User>() {
            @Override
            protected User doInBackground(Void... voids) {
                try {
                    return httpClient.readUserInfo(userId);
                } catch (IOException | JSONException e) {
                    e.printStackTrace();
                    return null;
                }
            }

            @Override
            protected void onPostExecute(User user) {
                displayUserInfo(user);
            }
        }.execute();
    }
    // остальной код ниже не изменился
}

 

Мы изменили:

  • Возвращаемый тип Asynctask со String на User.
  • Добавили в catch блок перехват исключения типа IOException | JSONException.
  • В onPostExecute мы заменили вызов Log.d() на вызов реального метода отображения displayUserInfo(user).

Давайте запустим наше приложение.

 

Result.png

 

Всё работает! Только видим, что наш ник выводится без символа @. Давайте изменим метод getNick() в классе User. Будем добавлять в него символ @ самостоятельно, т.к. сервер это не сделал.

 

User.java

1
2
3
4
5
6
7
public class User {
    // Остальной код выше не изменился
    public String getNick() {
        return "@" + nick;
    }
    // Остальной код ниже не изменился
}

 

Запустим наше приложение и увидим, что теперь с форматом поля nick всё в порядке:

 

ResultFinal.png

 

Полный листинг кода:

 

HttpClient.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package colibri.dev.com.colibritweet.network;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

import com.twitter.sdk.android.core.TwitterAuthConfig;
import com.twitter.sdk.android.core.TwitterCore;
import com.twitter.sdk.android.core.TwitterSession;
import com.twitter.sdk.android.core.internal.oauth.OAuth1aHeaders;

import org.json.JSONException;

import colibri.dev.com.colibritweet.pojo.User;

public class HttpClient {
    private static final String HEADER_AUTHORIZATION = "Authorization";
    private static final String GET = "GET";
    private final JsonParser jsonParser;

    public HttpClient(){
        jsonParser = new JsonParser();
    }

    public User readUserInfo(long userId) throws IOException, JSONException {
        String requestUrl = "https://api.twitter.com/1.1/users/show.json?user_id=" + userId;

        URL url = new URL(requestUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        // add auth header to request
        String authHeader = getAuthHeader(requestUrl);
        connection.setRequestProperty(HEADER_AUTHORIZATION, authHeader);

        connection.connect();

        InputStream in;
        int status = connection.getResponseCode();
        if (status != HttpURLConnection.HTTP_OK) {
            in = connection.getErrorStream();
        } else {
            in = connection.getInputStream();
        }

        String response = convertStreamToString(in);
        User user = jsonParser.getUser(response);

        return user;
    }

    private String convertStreamToString(InputStream stream) throws IOException {

        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
        StringBuilder sb = new StringBuilder();

        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line).append("\n");
        }
        stream.close();

        return sb.toString();
    }

    private String getAuthHeader(String url) {
        TwitterAuthConfig authConfig = TwitterCore.getInstance().getAuthConfig();
        TwitterSession session = TwitterCore.getInstance().getSessionManager().getActiveSession();

        return new OAuth1aHeaders().getAuthorizationHeader(authConfig,
                session.getAuthToken(), null, GET, url, null);
    }
}

 

JsonParser.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package colibri.dev.com.colibritweet.network;

import org.json.JSONException;
import org.json.JSONObject;

import colibri.dev.com.colibritweet.pojo.User;

public class JsonParser {

    public User getUser(String response) throws JSONException {
        JSONObject userJson = new JSONObject(response);
        long id = userJson.getLong("id");
        String name = userJson.getString("name");
        String nick = userJson.getString("screen_name");
        String location = userJson.getString("location");
        String description = userJson.getString("description");
        String imageUrl = userJson.getString("profile_image_url");
        int followersCount = userJson.getInt("followers_count");
        int followingCount = userJson.getInt("favourites_count");

        return new User(id, imageUrl, name, nick, description, location, followingCount, followersCount);
    }
}

 

UserInfoActivity.java

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package colibri.dev.com.colibritweet.activity;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ImageView;
import android.widget.TextView;

import com.squareup.picasso.Picasso;

import org.json.JSONException;

import colibri.dev.com.colibritweet.R;
import colibri.dev.com.colibritweet.adapter.TweetAdapter;
import colibri.dev.com.colibritweet.network.HttpClient;
import colibri.dev.com.colibritweet.pojo.Tweet;
import colibri.dev.com.colibritweet.pojo.User;

public class UserInfoActivity extends AppCompatActivity {
    private ImageView userImageView;
    private TextView nameTextView;
    private TextView nickTextView;
    private TextView descriptionTextView;
    private TextView locationTextView;
    private TextView followingCountTextView;
    private TextView followersCountTextView;

    private RecyclerView tweetsRecyclerView;
    private TweetAdapter tweetAdapter;
    private Toolbar toolbar;

    private HttpClient httpClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_info);

        // остальной код выше не изменился
        long userId = getIntent().getLongExtra(AuthActivity.USER_ID, -1);

        userImageView = findViewById(R.id.user_image_view);
        nameTextView = findViewById(R.id.user_name_text_view);
        nickTextView = findViewById(R.id.user_nick_text_view);
        descriptionTextView = findViewById(R.id.user_description_text_view);
        locationTextView = findViewById(R.id.user_location_text_view);
        followingCountTextView = findViewById(R.id.following_count_text_view);
        followersCountTextView = findViewById(R.id.followers_count_text_view);

        toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        initRecyclerView();

        httpClient = new HttpClient();
        loadUserInfo(userId);
        loadTweets();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.user_info_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if(item.getItemId() == R.id.action_search) {
            Intent intent = new Intent(this, SearchUsersActivity.class);
            startActivity(intent);
        }
        return true;
    }

    private void loadTweets() {
        Collection<Tweet> tweets = getTweets();
        tweetAdapter.setItems(tweets);
    }

    private Collection<Tweet> getTweets() {
        return Arrays.asList(
                new Tweet(getUser(), 1L, "Thu Dec 13 07:31:08 +0000 2017", "Очень длинное описание твита 1",
                        4L, 4L, "https://www.w3schools.com/w3css/img_fjords.jpg"),

                new Tweet(getUser(), 2L, "Thu Dec 12 07:31:08 +0000 2017", "Очень длинное описание твита 2",
                        5L, 5L, "https://www.w3schools.com/w3images/lights.jpg"),

                new Tweet(getUser(), 3L, "Thu Dec 11 07:31:08 +0000 2017", "Очень длинное описание твита 3",
                        6L, 6L, "https://www.w3schools.com/css/img_mountains.jpg")
        );
    }

    private void initRecyclerView() {
        tweetsRecyclerView = findViewById(R.id.tweets_recycler_view);
        tweetsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        tweetAdapter = new TweetAdapter();
        tweetsRecyclerView.setAdapter(tweetAdapter);
    }

    @SuppressLint("StaticFieldLeak")
    private void loadUserInfo(final Long userId) {
        new AsyncTask<Void, Void, User>() {
            @Override
            protected User doInBackground(Void... voids) {
                try {
                    return httpClient.readUserInfo(userId);
                } catch (IOException | JSONException e) {
                    e.printStackTrace();
                    return null;
                }
            }

            @Override
            protected void onPostExecute(User user) {
                displayUserInfo(user);
            }
        }.execute();
    }

    private void displayUserInfo(User user) {
        Picasso.with(this).load(user.getImageUrl()).into(userImageView);
        nameTextView.setText(user.getName());
        nickTextView.setText(user.getNick());
        descriptionTextView.setText(user.getDescription());
        locationTextView.setText(user.getLocation());

        String followingCount = String.valueOf(user.getFollowingCount());
        followingCountTextView.setText(followingCount);

        String followersCount = String.valueOf(user.getFollowersCount());
        followersCountTextView.setText(followersCount);
        getSupportActionBar().setTitle(user.getName());
    }

    private User getUser() {
        return new User(
                1L,
                "http://i.imgur.com/DvpvklR.png",
                "DevColibri",
                "devcolibri",
                "Sample description",
                "USA",
                42,
                42
        );
    }
}

 

Поздравляем, вы только что закончили полный цикл работы с сервером. Давайте подведём итоги:

  • Формат JSON широко используется для обмена данными между клиентом и сервером
  • Этот формат не привязан ни к какой технологии, поэтому он так широко используется
  • Структура данных представляет из себя ключ - значение, что позволяет удобно работать с ним
  • В Android есть класс JSONObject, который позволяет парсить JSON в любой необходимый формат данных
УВИДЕТЬ ВСЕ Добавить заметку
Вы
Добавить ваш комментарий
 

Сайт использует cookie-файлы для того, чтобы вам было удобнее им пользоваться. Для продолжения работы с сайтом, вам необходимо принять использование cookie-файлов.

Я ознакомлен(а)