Trang bị thêm GSON tuần tự hóa Ngày từ chuỗi json thành java.util.date


81

Tôi đang sử dụng thư viện Retrofit cho các cuộc gọi REST của mình. Hầu hết những gì tôi đã làm đều suôn sẻ như bơ nhưng vì một số lý do mà tôi gặp sự cố khi chuyển đổi chuỗi dấu thời gian JSON thành java.util.Dateđối tượng. JSON sắp xuất hiện trông như thế này.

{
    "date": "2013-07-16",
    "created_at": "2013-07-16T22:52:36Z",
} 

Làm cách nào để yêu cầu Retrofit hoặc Gson chuyển đổi các chuỗi này thành java.util.Date objects?


Có vẻ như một bản sao của câu hỏi này .
Davmrtl

@Davmrtl: nó không giống nhau vì có hai định dạng ngày khác nhau ở đây. Vì vậy, nó đòi hỏi một cách tiếp cận khác.
giampaolo

Câu trả lời:


162
Gson gson = new GsonBuilder()
    .setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
    .create();

RestAdapter restAdapter = new RestAdapter.Builder()
    .setEndpoint(API_BASE_URL)
    .setConverter(new GsonConverter.create(gson))
    .build();

Hoặc tương đương Kotlin:

val gson = GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create()
RestAdapter restAdapter = Retrofit.Builder()
    .baseUrl(API_BASE_URL)
    .addConverterFactory(GsonConverterFactory.create(gson))
    .build()
    .create(T::class.java)

Bạn có thể đặt trình phân tích cú pháp Gson tùy chỉnh của mình để trang bị thêm. Xem thêm tại đây: Trang web trang bị thêm

Hãy xem phản hồi của Ondreju để biết cách thực hiện điều này trong trang bị thêm 2.


8
Vui lòng giải thích ngắn gọn mã của bạn để nó hữu ích hơn cho OP và những người đọc khác.
Mohit Jain

Làm cách nào để thực hiện việc này cho tất cả các yêu cầu của tôi? Vì vậy, thêm nó vào của tôi ServiceGenerator?
Zapnologica

4
Điều này thực sự không áp dụng ngôn ngữ cho tôi. 2016-02-11T13:42:14.401Zđược deserialized đối tượng ngày tháng 13:42:14ở ngôn ngữ của tôi.
Alireza Mirian

Tôi đã thử điều này, Các mục đã tải xuống hiện đang hoạt động. Nhưng khi tôi đăng một Đối tượng Json lên máy chủ. Sau đó nó có xóa múi giờ của tôi không?
Zapnologica

Tôi đã dành rất nhiều thời gian để phát hiện sự cố làm ứng dụng của mình bị treo mà không có lý do gì sau khi thực hiện giải pháp này. Khi tôi sao chép và dán định dạng dete, một cái gì đó đã thay đổi dấu ngoặc kép từ 'T' thành 'T' - có một sự khác biệt - hãy xem kỹ!
LukaszTaraszka

86

Câu trả lời của @ gderaco đã được cập nhật lên trang bị thêm 2.0:

Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
.create();

Retrofit retrofitAdapter = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();

2
Để ý dấu ngoặc kép khi sao chép định dạng ngày - Tôi gặp sự cố với điều đó!
LukaszTaraszka

14

Đây là cách tôi đã làm điều đó:

Tạo lớp DateTime mở rộng Ngày và sau đó viết một trình giải mã tùy chỉnh:

public class DateTime extends java.util.Date {

    public DateTime(long readLong) {
        super(readLong);
    }

    public DateTime(Date date) {
        super(date.getTime());
    }       
}

Bây giờ đối với phần deserializer, nơi chúng tôi đăng ký cả hai trình chuyển đổi Date và DateTime:

public static Gson gsonWithDate(){
    final GsonBuilder builder = new GsonBuilder();

    builder.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {  

        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");  
        @Override  
        public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {  
            try {  
                return df.parse(json.getAsString());  
            } catch (final java.text.ParseException e) {  
                e.printStackTrace();  
                return null;  
            }  
        }
    });

    builder.registerTypeAdapter(DateTime.class, new JsonDeserializer<DateTime>() {  

        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
        @Override  
        public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {  
            try {  
                return new DateTime(df.parse(json.getAsString()));  
            } catch (final java.text.ParseException e) {
                e.printStackTrace();  
                return null;  
            }  
        }
    });

    return builder.create();
}

Và khi bạn tạo RestAdapter, hãy làm như sau:

new RestAdapter.Builder().setConverter(gsonWithDate());

Foo của bạn sẽ trông như thế này:

class Foo {
    Date date;
    DateTime created_at;
}

Cảm ơn! Tôi chỉ phải thay đổi từ return df.parse(json.getAsString()); đến long timeStamp = Long.parseLong(json.getAsString()); return new java.util.Date(timeStamp);
Juan Saravia

Tuyệt quá! Cảm ơn bạn!
NickUnuchek

7

Gson chỉ có thể xử lý một định dạng ngày giờ (những định dạng được chỉ định trong trình tạo) cộng với iso8601 nếu không thể phân tích cú pháp với định dạng tùy chỉnh. Vì vậy, một giải pháp có thể là viết trình giải mã tùy chỉnh của bạn. Để giải quyết vấn đề của bạn, tôi đã xác định:

package stackoverflow.questions.q18473011;

import java.util.Date;

public class Foo {

    Date date;
    Date created_at;

    public Foo(Date date, Date created_at){
       this.date = date;
       this.created_at = created_at;
    }

    @Override
    public String toString() {
       return "Foo [date=" + date + ", created_at=" + created_at + "]";
    }

}

với bộ khử không khí này:

package stackoverflow.questions.q18473011;

import java.lang.reflect.Type;
import java.text.*;
import java.util.Date;

import com.google.gson.*;

public class FooDeserializer implements JsonDeserializer<Foo> {

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

        String a = json.getAsJsonObject().get("date").getAsString();
        String b = json.getAsJsonObject().get("created_at").getAsString();

        SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat sdfDateWithTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");

        Date date, created;
        try {
           date = sdfDate.parse(a);
           created = sdfDateWithTime.parse(b);
        } catch (ParseException e) {
           throw new RuntimeException(e);
        }

        return new Foo(date, created);
    }

}

Bước cuối cùng là tạo một Gsonphiên bản với bộ điều hợp phù hợp:

package stackoverflow.questions.q18473011;

import com.google.gson.*;

public class Question {

    /**
     * @param args
     */
    public static void main(String[] args) {
      String s = "{ \"date\": \"2013-07-16\",    \"created_at\": \"2013-07-16T22:52:36Z\"}";


      GsonBuilder builder = new GsonBuilder();
      builder.registerTypeAdapter(Foo.class, new FooDeserializer());

      Gson gson = builder.create();
      Foo myObject = gson.fromJson(s, Foo.class);

      System.out.println("Result: "+myObject);
    }

}

Kết quả của tôi:

Result: Foo [date=Tue Jul 16 00:00:00 CEST 2013, created_at=Tue Jul 16 22:52:36 CEST 2013]

1
Điều gì sẽ xảy ra nếu tôi có 15-20 trường bên trong Foo (bao gồm các đối tượng tùy chỉnh), tôi phải giải mã hóa chúng theo cách thủ công, đúng không. Điều đó giết chết mục đích của Gson sau đó.
Farid

1

Hoàn toàn đúng theo nghĩa đen nếu bạn đã có một đối tượng Date với tên "create_at" trong lớp bạn đang tạo thì thật dễ dàng như sau:

Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").create();
YourObject parsedObject1 = gson.fromJson(JsonStringYouGotSomehow, YourObject.class);

Và bạn đã hoàn thành. không cần ghi đè phức tạp.


Bạn nên thử nó với Retrofit trước khi "Và bạn đã hoàn tất. Không cần ghi đè phức tạp."
Farid

1

Bạn có thể xác định hai lớp mới như sau:

import java.util.Date;

public class MyDate extends Date {
}

import java.util.Date;

public class CreatedAtDate extends Date {
}

POJO của bạn sẽ như thế này:

import MyDate;
import CreatedAtDate;

public class Foo {
    MyDate date;
    CreatedAtDate created_at;
}

Cuối cùng, đặt bộ khử không khí tùy chỉnh của bạn:

public class MyDateDeserializer implements JsonDeserializer<Date> {

    public static final SimpleDateFormat sServerDateDateFormat = new SimpleDateFormat("yyyy-MM-dd");

    @Override
    public MyDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
         if (json != null) {
            final String jsonString = json.getAsString();
            try {
                return (MyDate) sServerDateDateFormat.parse(jsonString);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MyDate.class, new MyDateDeserializer());

0

Điều này không trả lời trực tiếp câu hỏi được hỏi, nhưng theo ý kiến ​​của tôi là "hiện đại" nếu người lập trình có toàn quyền lựa chọn về cách giải quyết vấn đề.

Trước hết, nó không phải là giải pháp tốt nhất để sử dụng java.util.Date. Lý do là các lớp đó không có hành vi lý tưởng trong một số trường hợp góc, vì vậy nơi được thay thế bởi lớp Java Instant, v.v. hãy kiểm tra câu trả lời của Basil Bourque trong câu hỏi SO này: Tạo đối tượng Ngày trong Kotlin cho cấp API nhỏ hơn hoặc bằng 16

Vì vậy, tôi đã sử dụng lớp Instant của ThreeTenABP và sử dụng Kotlin, trên Android:

val gson = GsonBuilder().registerTypeAdapter(Instant::class.java,
    JsonDeserializer<Instant> { json: JsonElement, _: Type?, _: JsonDeserializationContext? ->
        ZonedDateTime.parse(
            json.asJsonPrimitive.asString
        ).toInstant()
    }
).create()

val retrofit = Retrofit.Builder()
    .baseUrl(baseUrl)
    .addConverterFactory(GsonConverterFactory.create(gson))
    .build()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.