如何使用 Jackson ObjectMapper 序列化从 JSON 响应中替换空字段(嵌套在所有级别)?

How to replace null fields (nested at all levels) from JSON response using Jackson ObjectMapper serialization?

我正在使用以下代码以 JSON 响应的形式从 Twitter4j 搜索 API 接收推文。我在 Twitter4j 搜索 API 行

中指定的列表形式接收结果
List<Status> tweets = result.getTweets();

问题是推文作为列表返回,其中一个状态条目具有非空和非空的 GeoLocation,而另一个状态条目具有空的 GeoLocation。由于要从每个 Status 条目(即 Tweet)中检索相关字段,我遍历 List 并调用 getter,这对于 GeoLocation 字段为 null 的 Status 条目抛出 null。

我尝试遵循的方法:

我用相关字段及其 getter 和 setter 创建了一个 POJO TweetJSON_2(定义在 post 的底部)。我正在使用 Jackson ObjectMapper 来处理空值,如下所示:

    JsonGenerator generator = new JsonFactory().createGenerator(os);
                    generator.setPrettyPrinter(new DefaultPrettyPrinter());
                    TweetJSON_2 rawJSON; 

                    ObjectMapper mapper = new ObjectMapper();
                    //mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
                    mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
                    mapper.setSerializationInclusion(Include.NON_NULL);
// ... rawJSON is populated ...
    mapper.writeValue(generator, rawJSON);

但是,当我尝试从 Status 获取 geoLocation 字段时,使用下面标有 **

的行
List<Status> tweets = result.getTweets();

我收到如下 Java NullPointerException:

[Mon Apr 20 11:32:47 IST 2015]{"statuses":[{"retweeted_status":{"contributors":null,"text":"<my text>",**"geo":null**,"retweeted":false,"in_reply_to_screen_name":null,"truncated":false,"lang":"en","entities":{"symbols":[],"urls":[],"hashtags": ... &include_entities=1","since_id_str":"0","completed_in":0.029}}

    **Exception in thread "main" java.lang.NullPointerException
        at analytics.search.twitter.SearchFieldsTweetsJSON_2.main(SearchFieldsTweetsJSON_2.java:78)**

例如:如果我输入一个 json 字符串作为

String s = "{\"first\": 123, \"second\": [{\"second_first\":null, \"second_second\":null}, {\"second_third\":null}, null], \"third\": 789, \"fourth\":null}";

输出应该像

"{\"first\": 123, \"third\": 789}";

我想要的是替换 JSON 数组中的所有空元素和 JSON 对象中的所有空键值对,无论它们嵌套在什么级别我的 JSON 回复。

对象与树模型方法

我尝试了对象模型解析机制,这是一种基于 javax.json.stream.JsonParser.Event 的方法,但需要多次访问和对 JSON 字符串进行对象替换,具体取决于 null 的嵌套级别这种做法很复杂。同时,如果我使用树模型机制,整个 JSON 响应必须存储为树,这可能会溢出我的 JVM 堆内存,因为根据我的查询 JSON 大小可能非常大参数。我需要找到一个可行的解决方案来克服这个问题。对于解决上述问题的任何建议,我们将不胜感激。

代码如下:

public class SearchFieldsTweetsJSON_2 {
    /* Searches specific fields from Tweets in JSON format */

    public static void main(String[] args) throws FileNotFoundException, IOException {
        if (args.length < 2) {
            System.out.println("java twitter4j.examples.search.SearchTweets [query][outputJSONFile]");
            System.exit(-1);
        }
        ConfigurationBuilder cb = new ConfigurationBuilder();
        cb.setDebugEnabled(true)
        .setOAuthConsumerKey("XXXXXXXXXXXXXXXXXXXXXXXXXX")
        .setOAuthConsumerSecret("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
        .setOAuthAccessToken("NNNNNNNNN-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
        .setOAuthAccessTokenSecret("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
        .setJSONStoreEnabled(true);
        Twitter twitter = new TwitterFactory(cb.build()).getInstance(); 
        try {
            Query query = new Query(args[0]);
            QueryResult result;
            File jsonFile = new File(args[1]);
            System.out.println("File Path : " + jsonFile.getAbsolutePath());
            OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(jsonFile));

            JsonGenerator generator = new JsonFactory().createGenerator(os);
            generator.setPrettyPrinter(new DefaultPrettyPrinter());
            TweetJSON_2 rawJSON; 

            ObjectMapper mapper = new ObjectMapper();
            //mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
            mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
            mapper.setSerializationInclusion(Include.NON_NULL);

            do {
                result = twitter.search(query);
                List<Status> tweets = result.getTweets();
                for (Status tweet : tweets) {
                    rawJSON = new TweetJSON_2();
                    rawJSON.setStatusId(Long.toString(tweet.getId()));
                    rawJSON.setUserId(Long.toString(tweet.getUser().getId()));
                    rawJSON.setUserName(tweet.getUser().getScreenName());
                    rawJSON.setStatusText(tweet.getText());
                    rawJSON.setGeoLocation(tweet.getGeoLocation().toString()); **<< Giving error at tweet.getGeoLocation() since GeoLocation is null**
                    mapper.writeValue(generator, rawJSON);
                    System.out.println(rawJSON.toString());
                }
            } while ((query = result.nextQuery()) != null); 
            generator.close();
            System.out.println(os.toString());
        } catch (TwitterException te) {
            te.printStackTrace();
            System.out.println("Failed to search tweets : " + te.getMessage());
            System.exit(-1);
        } 
    }

}

我已将 TweetJSON_2 Java 对象定义如下:

public class TweetJSON_2 {

    public  String statusId;
    public  String statusText;
    public  String userId;
    public  String userName;
    public  String geoLocation;

    public String getStatusId() {
        return statusId;
    }
    public void setStatusId(String statusId) {
        this.statusId = statusId;
    }
    public String getStatusText() {
        return statusText;
    }
    public void setStatusText(String statusText) {
        this.statusText = statusText;
    }
    public String getUserId() {
        return userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getGeoLocation() {
        return geoLocation;
    }
    public void setGeoLocation(String geoLocation) {
        this.geoLocation = geoLocation;
    }
    @Override
    public String toString() {
        return "TweetJSON_2 [ statusId = " + statusId + ", statusText = " + statusText + "]";
    }
}

我已经尝试通过以下方式重新配置我的 POJO,它成功地替换了 setter 方法中指定的所有空值。不需要遵循 JSON 字符串的树或基于事件的模型解析。 HTH

修改后的TweetJSON_2 POJO:

public class TweetJSON_2 {
    public  Long statusId = null;
    public  String statusText = null;
    public  Long userId = null;
    public  String userName = null;
    public  GeoLocation geoLocation = null;

    public Long getStatusId() {
        if (this.statusId==null)
            return new Long(0L);
        return statusId;
    }
    public void setStatusId(Long statusId) {
        if (statusId==null)
            this.statusId = new Long(0L);
        else
            this.statusId = statusId;
    }
    public String getStatusText() {
        if (this.statusText==null)
            return new String("");
        return statusText;
    }
    public void setStatusText(String statusText) {
        if (statusText==null)
            this.statusText = new String("");
        else
            this.statusText = statusText;
    }
    public Long getUserId() {
        if (this.userId==null)
            return new Long(0L);
        return userId;
    }
    public void setUserId(Long userId) {
        if (userId==null)
            this.userId = new Long(0L);
        else
            this.userId = userId;
    }
    public String getUserName() {
        if (this.userName==null)
            return new String("");
        return userName;
    }
    public void setUserName(String userName) {
        if (userName==null)
            this.userName = new String("");
        else
            this.userName = userName;
    }
    public GeoLocation getGeoLocation() {
        if (this.geoLocation==null)
            return new GeoLocation(0.0,0.0);
        return geoLocation;
    }
    public void setGeoLocation(GeoLocation geoLocation) {
        if (geoLocation==null)
            this.geoLocation = new GeoLocation(0.0,0.0);
        else
            this.geoLocation = geoLocation;
    }
    @Override
    public String toString() {
        return "TweetJSON_2 [ statusId = " + statusId + ", statusText = " + statusText + "]";
    }
}