小编典典

如何在Android中基于动态SerializedName用gson解析json对象

java

我如何使用Gson这个网址解析这个json物件:http://apis.skplanetx.com/weather/forecast/3hours
appKey =
4ce0462a-3884-30ab-ab13-93efb1bc171f&version = 1&lon = 127.9259&lat = 36.991

    "wind": {
      "wdir3hour": "176.00",
      "wspd3hour": "3.10",
      "wdir4hour": "",
      "wspd4hour": "",
      "wdir1hour": "173.00",
      "wspd1hour": "3.60",
      "wdir2hour": "175.00",
      "wspd2hour": "3.40"
    },

    "precipitation": {
      "sinceOntime1hour": "0.00",
      "type1hour": "0",
      "sinceOntime2hour": "0.00",
      "type2hour": "0",
      "sinceOntime3hour": "0.00",
      "type3hour": "0",
      "sinceOntime4hour": "",
      "type4hour": ""
    },

我如何为该json对象编写Java模型。我不想写每个这样的变量。

 class Wind{
 @SerializedName("wdir1hour")
 private String wdir1hour;  
 @SerializedName("wdir2hour")
 private String wdir2hour; 
 @SerializedName("wdir3hour")
 private String wdir3hour; 
 @SerializedName("wdir4hour")
 private String wdir4hour; 
 }

谁对这个json对象有更好的主意?


阅读 284

收藏
2020-11-26

共1个答案

小编典典

通过您提供的链接可访问的当前JSON响应似乎存在一些设计问题或怀疑。我将在此处发布JSON,以免将来丢失它:

{
    "weather": {
        "forecast3hours": [
            {
                "grid": {
                    "city": "충북",
                    "county": "충주시",
                    "village": "목행동",
                    "latitude": "37.0135600000",
                    "longitude": "127.9036500000"
                },
                "lightning1hour": "0",
                "timeRelease": "2017-02-24 16:30:00",
                "wind": {
                    "wspd2hour": "3.10",
                    "wdir1hour": "179.00",
                    "wspd1hour": "4.20",
                    "wdir2hour": "176.00",
                    "wdir3hour": "",
                    "wspd3hour": "",
                    "wdir4hour": "",
                    "wspd4hour": ""
                },
                "precipitation": {
                    "sinceOntime1hour": "0.00",
                    "type1hour": "0",
                    "sinceOntime2hour": "0.00",
                    "type2hour": "0",
                    "sinceOntime3hour": "",
                    "type3hour": "",
                    "sinceOntime4hour": "",
                    "type4hour": ""
                },
                "sky": {
                    "code1hour": "SKY_V01",
                    "name1hour": "맑음",
                    "code2hour": "SKY_V01",
                    "name2hour": "맑음",
                    "code3hour": "",
                    "name3hour": "",
                    "code4hour": "",
                    "name4hour": ""
                },
                "temperature": {
                    "temp1hour": "3.20",
                    "temp2hour": "2.00",
                    "temp3hour": "",
                    "temp4hour": ""
                },
                "humidity": {
                    "rh1hour": "41.00",
                    "rh2hour": "50.00",
                    "rh3hour": "",
                    "rh4hour": ""
                },
                "lightning2hour": "0",
                "lightning3hour": "",
                "lightning4hour": ""
            }
        ]
    },
    "common": {
        "alertYn": "N",
        "stormYn": "N"
    },
    "result": {
        "code": 9200,
        "requestUrl": "/weather/forecast/3hours?lon=127.9259&lat=36.991&version=1&appKey=4ce0462a-3884-30ab-ab13-93efb1bc171f",
        "message": "성공"
    }
}

从我的角度来看,它们是:

  • 没有数组,而是人为索引的对象键(这是您的问题的主题)。
  • 空字符串表示可能的null值,而不是nulls或仅将其排除在响应之外。
  • 几乎所有的值都表示为字符串文字,即使它们似乎是非字符串也是如此。
  • 布尔值似乎用Yn后缀标记,分别定义truefalse使用"Y""N"

这就是为什么自动POJO生成器可能不是最佳的处理方式,因为它们可能无法检测到特定JSON字符串值的“真实”类型,而且它们无法生成自定义反序列化器。不知道为什么要这样设计,但是您可以设计自定义映射,使其对程序更加友好,并且可以对其进行更多控制。

final class Response {

    final Weather weather = null;
    final Common common = null;
    final Result result = null;

    @Override
    public String toString() {
        return new StringBuilder("Response{")
                .append("weather=").append(weather)
                .append(", common=").append(common)
                .append(", result=").append(result)
                .append('}').toString();
    }

}

final class Weather {

    final List<Forecast> forecast3hours = null;

    @Override
    public String toString() {
        return new StringBuilder("Weather{")
                .append("forecast3hours=").append(forecast3hours)
                .append('}').toString();
    }

}

final class Forecast {

    final Grid grid;
    final Date timeRelease;
    final List<Integer> lightnings;
    final List<Wind> winds;
    final List<Precipitation> precipitations;
    final List<Sky> skies;
    final List<Float> temperatures;
    final List<Float> humidities;

    Forecast(final Grid grid, final Date timeRelease, final List<Integer> lightnings, final List<Wind> winds, final List<Precipitation> precipitations,
            final List<Sky> skies, final List<Float> temperatures, final List<Float> humidities) {
        this.grid = grid;
        this.timeRelease = timeRelease;
        this.lightnings = lightnings;
        this.winds = winds;
        this.precipitations = precipitations;
        this.skies = skies;
        this.temperatures = temperatures;
        this.humidities = humidities;
    }

    @Override
    public String toString() {
        return new StringBuilder("Forecast{")
                .append("grid=").append(grid)
                .append(", timeRelease=").append(timeRelease)
                .append(", lightnings=").append(lightnings)
                .append(", winds=").append(winds)
                .append(", precipitations=").append(precipitations)
                .append(", skies=").append(skies)
                .append(", temperatures=").append(temperatures)
                .append(", humidities=").append(humidities)
                .append('}').toString();
    }

}

final class Grid {

    final String city = null;
    final String county = null;
    final String village = null;
    final double latitude = Double.valueOf(0); // disable inlining the primitive double 0
    final double longitude = Double.valueOf(0); // disable inlining the primitive double 0

    @Override
    public String toString() {
        return new StringBuilder("Grid{")
                .append("city='").append(city).append('\'')
                .append(", county='").append(county).append('\'')
                .append(", village='").append(village).append('\'')
                .append(", latitude=").append(latitude)
                .append(", longitude=").append(longitude)
                .append('}').toString();
    }

}

final class Wind {

    final float speed;
    final float direction;

    Wind(final float speed, final float direction) {
        this.speed = speed;
        this.direction = direction;
    }

    @Override
    public String toString() {
        return new StringBuilder("Wind{")
                .append("speed=").append(speed)
                .append(", direction=").append(direction)
                .append('}').toString();
    }

}

final class Precipitation {

    final float sinceOntime;
    final int type;

    Precipitation(final float sinceOntime, final int type) {
        this.sinceOntime = sinceOntime;
        this.type = type;
    }

    @Override
    public String toString() {
        return new StringBuilder("Precipitation{")
                .append("sinceOntime='").append(sinceOntime).append('\'')
                .append(", type=").append(type)
                .append('}').toString();
    }

}

final class Sky {

    final String code;
    final String name;

    Sky(final String code, final String name) {
        this.code = code;
        this.name = name;
    }

    @Override
    public String toString() {
        return new StringBuilder("Sky{")
                .append("code='").append(code).append('\'')
                .append(", name='").append(name).append('\'')
                .append('}').toString();
    }

}

final class Common {

    @SerializedName("alertYn")
    @JsonAdapter(YnToBooleanJsonDeserializer.class)
    final boolean alert = Boolean.valueOf(false); // disable inlining the primitive boolean false

    @SerializedName("stormYn")
    @JsonAdapter(YnToBooleanJsonDeserializer.class)
    final boolean storm = Boolean.valueOf(false); // disable inlining the primitive boolean false

    @Override
    public String toString() {
        return new StringBuilder("Common{")
                .append("alert=").append(alert)
                .append(", storm=").append(storm)
                .append('}').toString();
    }

}

final class Result {

    final int code = Integer.valueOf(0); // disable inlining the primitive int 0
    final String requestUrl = null;
    final String message = null;

    @Override
    public String toString() {
        return new StringBuilder("Result{")
                .append("code=").append(code)
                .append(", requestUrl='").append(requestUrl).append('\'')
                .append(", message='").append(message).append('\'')
                .append('}').toString();
    }

}

其中一些映射具有显式构造函数-
必须在自定义反序列化器中手动实例化此类对象。如果没有提供构造函数,那么Gson可以处理这种映射本身,而该映射只具有有关应如何反序列化特定对象的足够信息。

由于应该以非标准方式解析数据,因此可以实现几个自定义反序列化器。以下类型适配器分别将"Y"和转换"N"truefalse

final class YnToBooleanJsonDeserializer
        implements JsonDeserializer<Boolean> {

    // Gson will instantiate this adapter itself
    private YnToBooleanJsonDeserializer() {
    }

    @Override
    public Boolean deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
            throws JsonParseException {
        final String rawFlag = jsonElement.getAsString();
        switch ( rawFlag ) {
        case "N":
            return false;
        case "Y":
            return true;
        default:
            throw new JsonParseException("Can't parse: " + rawFlag);
        }
    }

}

下一个JsonDeserializer尝试xxx<N>hour使用正则表达式检测类似键,并提取<N>索引以构建构造Forecast实例所需的列表。请注意,它可以解析任意大小的“列表”(JSON中的列表)。

final class ForecastJsonDeserializer
        implements JsonDeserializer<Forecast> {

    // This deserializer does not hold any state and can be instantiated once per application life-cycle.
    private static final JsonDeserializer<Forecast> forecastJsonDeserializer = new ForecastJsonDeserializer();

    private ForecastJsonDeserializer() {
    }

    static JsonDeserializer<Forecast> getForecastJsonDeserializer() {
        return forecastJsonDeserializer;
    }

    @Override
    public Forecast deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
            throws JsonParseException {
        final JsonObject jsonObject = jsonElement.getAsJsonObject();
        return new Forecast(
                context.deserialize(jsonObject.get("grid"), Grid.class),
                context.deserialize(jsonObject.get("timeRelease"), Date.class),
                lightningsExtractor.parseList(jsonObject),
                windsExtractor.parseList(jsonObject.get("wind").getAsJsonObject()),
                precipitationsExtractor.parseList(jsonObject.get("precipitation").getAsJsonObject()),
                skiesExtractor.parseList(jsonObject.get("sky").getAsJsonObject()),
                temperaturesExtractor.parseList(jsonObject.get("temperature").getAsJsonObject()),
                humiditiesExtractor.parseList(jsonObject.get("humidity").getAsJsonObject())
        );
    }

    private static final AbstractExtractor<Integer> lightningsExtractor = new AbstractExtractor<Integer>(compile("lightning(\\d)hour")) {
        @Override
        protected Integer parse(final int index, final JsonObject jsonObject) {
            final String rawLightning = jsonObject.get("lightning" + index + "hour").getAsString();
            if ( rawLightning.isEmpty() ) {
                return null;
            }
            return parseInt(rawLightning);
        }
    };

    private static final AbstractExtractor<Wind> windsExtractor = new AbstractExtractor<Wind>(compile("(?:wdir|wspd)(\\d)hour")) {
        @Override
        protected Wind parse(final int index, final JsonObject jsonObject) {
            String rawSpeed = jsonObject.get("wspd" + index + "hour").getAsString();
            String rawDirection = jsonObject.get("wdir" + index + "hour").getAsString();
            if ( rawSpeed.isEmpty() && rawDirection.isEmpty() ) {
                return null;
            }
            return new Wind(parseFloat(rawSpeed), parseFloat(rawDirection));
        }
    };

    private static final AbstractExtractor<Precipitation> precipitationsExtractor = new AbstractExtractor<Precipitation>(compile("(?:sinceOntime|type)(\\d)hour")) {
        @Override
        protected Precipitation parse(final int index, final JsonObject jsonObject) {
            final String rawSinceOntime = jsonObject.get("sinceOntime" + index + "hour").getAsString();
            final String rawType = jsonObject.get("type" + index + "hour").getAsString();
            if ( rawSinceOntime.isEmpty() && rawType.isEmpty() ) {
                return null;
            }
            return new Precipitation(parseFloat(rawSinceOntime), parseInt(rawType));
        }
    };

    private static final AbstractExtractor<Sky> skiesExtractor = new AbstractExtractor<Sky>(compile("(?:code|name)(\\d)hour")) {
        @Override
        protected Sky parse(final int index, final JsonObject jsonObject) {
            final String rawCode = jsonObject.get("code" + index + "hour").getAsString();
            final String rawName = jsonObject.get("name" + index + "hour").getAsString();
            if ( rawCode.isEmpty() && rawName.isEmpty() ) {
                return null;
            }
            return new Sky(rawCode, rawName);
        }
    };

    private static final AbstractExtractor<Float> temperaturesExtractor = new AbstractExtractor<Float>(compile("temp(\\d)hour")) {
        @Override
        protected Float parse(final int index, final JsonObject jsonObject) {
            final String rawTemperature = jsonObject.get("temp" + index + "hour").getAsString();
            if ( rawTemperature.isEmpty() ) {
                return null;
            }
            return parseFloat(rawTemperature);
        }
    };

    private static final AbstractExtractor<Float> humiditiesExtractor = new AbstractExtractor<Float>(compile("rh(\\d)hour")) {
        @Override
        protected Float parse(final int index, final JsonObject jsonObject) {
            final String rawHumidity = jsonObject.get("rh" + index + "hour").getAsString();
            if ( rawHumidity.isEmpty() ) {
                return null;
            }
            return parseFloat(rawHumidity);
        }
    };

    private abstract static class AbstractExtractor<T> {

        private final Pattern pattern;

        private AbstractExtractor(final Pattern pattern) {
            this.pattern = pattern;
        }

        protected abstract T parse(int index, JsonObject jsonObject);

        private List<T> parseList(final JsonObject jsonObject) {
            final List<T> list = new ArrayList<>();
            for ( final Entry<String, JsonElement> e : jsonObject.entrySet() ) {
                final String key = e.getKey();
                final Matcher matcher = pattern.matcher(key);
                // Check if the given regular expression matches the key
                if ( matcher.matches() ) {
                    // If yes, then just extract and parse the index 
                    final int index = parseInt(matcher.group(1));
                    // And check if there is enough room in the result list because the JSON response may contain unordered keys
                    while ( index > list.size() ) {
                        list.add(null);
                    }
                    // As Java lists are 0-based
                    if ( list.get(index - 1) == null ) {
                        // Assuming that null marks an object that's probably not parsed yet
                        list.set(index - 1, parse(index, jsonObject));
                    }
                }
            }
            return list;
        }

    }

}

现在可以将它们放在一起:

public static void main(final String... args) {
    final Gson gson = new GsonBuilder()
            .setDateFormat("yyyy-MM-dd hh:mm:ss")
            .registerTypeAdapter(Forecast.class, getForecastJsonDeserializer())
            .create();
    final Response response = gson.fromJson(JSON, Response.class);
    System.out.println(response);
}

输出:

响应{天气=天气{forecast3hours = [预测{grid = Grid {city =’충북’,县=’충주시’,村庄=’목행동’,纬度=
37.01356,经度= 127.90365}},timeRelease =周五2月24日30:30 :00 EET 2017,闪电=
[0,0,null,null],风= [风{speed = 4.2,方向= 179.0},风{speed = 3.1,方向=
176.0},null,null],降水量= [降水{sinceOntime =‘0.0’,类型= 0},降水{sinceOntime
=‘0.0’,类型= 0},null,null],天空= [Sky {code =’SKY_V01’,name =’맑음’},Sky {code
=’SKY_V01’,name =’맑음’},null,null],温度= [3.2,2.0,null,null],湿度=
[41.0,50.0,null,null]}]},common = Common { alert = false,storm =
false},result = Result {code = 9200,requestUrl =’/ weather / forecast /
3hours?lon = 127.9259&lat = 36.991&version = 1&appKey = 4ce0462a-3884-30ab-
ab13-93efb1bc171f’,message =’성공’}}

2020-11-26