JavaでJSONの値がNULLかどうか検証、JSONって入れ子になってることが多いけど、Jackson編

jacksonで検索すると、アメリカの楽器メーカーの Jackson Guitars とか、歌手のMichael Jackson 氏とかヒットするけれど、今回は、

github.com

This is the home page of the Jackson Project, formerly known as the standard JSON library for Java (or JVM platform in general), or, as the "best JSON parser for Java." Or simply as "JSON for Java." More than that, Jackson is a suite of data-processing tools for Java (and the JVM platform), including the flagship streaming JSON parser / generator library, matching data-binding library (POJOs to and from JSON) and additional data format modules to process data encoded in AvroBSON,CBORCSVSmile(Java) PropertiesProtobufXML or YAML; and even the large set of data format modules to support data types of widely used data types such as GuavaJodaPCollections and many, many more (see below).

https://github.com/FasterXML/jackson

JavaJSONライブラリのJacksonの話でございます。

ジャクソン5 は「Jackson 5」で検索しないとヒットしなかったという...

というわけで、Jacksonにトライ。 

 

 

Jacksonを導入してみる

Jacksonについては、 

gihyo.jp

gihyo.jp

⇧ 上記サイト様が詳しいです。

 

というわけで、まずは、Jacksonライブラリをダウンロードして配置ですかね。(Maven、Gradle、Bazel などのビルドツールとか使える環境であれば、ライブラリを依存関係とかも良しなに取り計らってインストールくれるようですが)

では、https://github.com/FasterXML/jackson にアクセスし、

f:id:ts0818:20180917122136p:plain

スクロールすると、「Recommended way to use Jackson is through Maven repositories; releases are made to Central Maven Repository (CMR). 」ってある、というか、おすすめっていう割には、「Central Maven Repository」へのリンク作ってないんだ...

f:id:ts0818:20180917122323p:plain

というわけで、https://mvnrepository.com/repos/central にアクセス。

f:id:ts0818:20180917123540p:plain

「jackson」で検索。

f:id:ts0818:20180917123622p:plain

かなりヒットするんですが、「Jackson Databind」「Jackson Core」「Jackson Annotations」の3つがあれば良いらしい。

f:id:ts0818:20180917124748p:plain

Java Jacksonを使用したJSONの変換のサンプル |

⇧  上記サイト様によりますと、Mavenを使える場合は、「Jackson Core」をインストールするよう、pom.xmlに記述すると、他の2つのライブラリも勝手にインストールしてくれるんだとか。

今回は、Mavenを使ってないので、3つ全てダウンロードします。

まずは、「Jackson Core」をクリック。

f:id:ts0818:20180917125326p:plain

ページ遷移するので、今回は、「Version」が「2.9.6」のリンクをクリック。

f:id:ts0818:20180917125428p:plain

「Files」の「View All」のリンクをクリック。

f:id:ts0818:20180917125710p:plain

「jackson-core-2.9.6.jar」を選択。

f:id:ts0818:20180917125918p:plain

「保存」をクリック。

f:id:ts0818:20180917130014p:plain

保存されました。 

f:id:ts0818:20180917130048p:plain

「Jackson Databind」「Jackson Annotations」についても同じ手順でjarファイルをダウンロードします。

f:id:ts0818:20180917130255p:plain

ダウンロードされました。

f:id:ts0818:20180917130450p:plain

 

Eclipseを起動し、 「ファイル(F)」>「新規(N)」>「Java プロジェクト」を選択。

f:id:ts0818:20180917130741p:plain

「プロジェクト名(P):」を適当に付けて、「次へ(N) >」をクリック。

f:id:ts0818:20180917130911p:plain

「完了(F)」をクリック。

f:id:ts0818:20180917131043p:plain

「パッケージ・エクスプローラー」にプロジェクトが作成されるので、プロジェクトを選択した状態で右クリックし、「新規(W)」>「フォルダー」をクリック。 

f:id:ts0818:20180917131209p:plain

プロジェクトディレクトリの直下(ここでは、「Jackson Test」)に、ライブラリ配置用のディレクトリを作成。「フォルダー名(N):」は適当に。「完了(F)」 をクリック。

f:id:ts0818:20180917131419p:plain

 作成した、ライブラリ配置用のディレクトリに、ダウンロードしておいたライブラリをドラッグ&ドロップします。

f:id:ts0818:20180917131720p:plain

「ファイルをコピー(C)」にチェックされた状態で、「OK」。 

f:id:ts0818:20180917131831p:plain

配置されました。 

f:id:ts0818:20180917131926p:plain

ライブラリを使えるようにビルドパスに追加します。

「パッケージ・エクスプローラー」でプロジェクトを選択した状態で右クリックし、「ビルド・パス(B)」>「外部アーカイブの追加(V)...」を選択。 

f:id:ts0818:20180917132137p:plain

配置しておいたライブラリを選択し、「開く(O)」。(Shiftを押しながらクリックで複数ファイルを選択できます。全選択は、フォルダ内で、Ctrl + A で。)

f:id:ts0818:20180917132443p:plain

「参照ライブラリー」が追加され、その中にライブラリがあればOKです。 

f:id:ts0818:20180917132511p:plain

 

Jacksonを使ってみる

Jacksonが使える準備が整いましたので、クラスファイルを作成します。 

プロジェクトを選択した状態で右クリックし、「新規(W)」>「クラス」を選択。

f:id:ts0818:20180917133945p:plain

「パッケージ(K):」「名前(M):」を適当に入力し、「public static void main(String[] args)(V)」にチェックし、「完了(F)」をクリック。

f:id:ts0818:20180917134154p:plain

クラスができました。 

f:id:ts0818:20180917134424p:plain

 

で、今回も、 

{
    "id": "957c43f2-fa2e-42f9-bf75-6e3d5bb6960a",
    "name": "The Best Product",
    "brand": {
        "id": "9bcd817d-0141-42e6-8f04-e5aaab0980b6",
        "name": "Baccarat",
        "owner": {
            "id": "b21a80b1-0c09-4be3-9ebd-ea3653511c13",
            "name": "Fortune Legend Limited",
            "manager" : {
                "id" : "98jughul-078b-ih09-kju9-p09juhugh086",
                "name" : null,
                "employee" : {
                    "id" : "98jughul-078b-ih09-kju9-p09lhrs54676",
                    "name" : null
                }
            }
        }
    }  
}   

のようなJSONデータが、外部から渡された体で。

例のごとく、 

ja.infobyip.com

で、JSONデータをエンコードしておきます。

f:id:ts0818:20180917134955p:plain

で、なんやかんややってみて、どうも、JacksonでJSONデータのNULLを空文字に置換する処理が必要そう。

qiita.com

ja.stackoverflow.com

⇧  上記サイト様によりますと、 なんか、Jacksonでパーサー(解析)をする前に、JSONデータのNULLを空文字に置換するしかなさそう。

stackoverrun.com

⇧  上記サイト様とかの方法だと、JsonNodeがNULLの場合に、エラーになるっぽい

((ObjectNode)jsonNode).put("value", "");     

みたいなことを、JsonNodeがNULLの場合にやろうとすると、

Exception in thread "main" java.lang.ClassCastException: com.fasterxml.jackson.databind.node.NullNode cannot be cast to com.fasterxml.jackson.databind.node.ObjectNode
	at ts0818.jackson.test.JacksonTest.objectNullCheck(JacksonTest.java:95)
	at ts0818.jackson.test.JacksonTest.objectNullCheck(JacksonTest.java:92)
	at ts0818.jackson.test.JacksonTest.objectNullCheck(JacksonTest.java:92)
	at ts0818.jackson.test.JacksonTest.nodeTypeChecker(JacksonTest.java:56)
	at ts0818.jackson.test.JacksonTest.lambda$0(JacksonTest.java:35)
	at java.lang.Iterable.forEach(Iterable.java:75)
	at ts0818.jackson.test.JacksonTest.main(JacksonTest.java:34)
    

みたいなエラーが。

qiita.com

Jacksonが提供するObjectMapperに独自定義したシリアライザーをDefaultSerializerProviderを通して設定してやればよい。

SpringBootでJackson使用時にnullを空文字として扱いたい

⇧  上記サイト様によりますと、そういうことみたいです。Spring Bootではないですが、やってみますか。

 

リアライザー用のクラスを1つ追加します。

f:id:ts0818:20180917171445p:plain

「パッケージ(K):」「名前(M):」を適当に入力し、「完了(F)」をクリック。 

f:id:ts0818:20180917171532p:plain

NullValueSerializer.java

package ts0818.jackson.test;

import java.io.IOException;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

public class NullValueSerializer extends JsonSerializer<Object> {

	@Override
	public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
			throws IOException, JsonProcessingException {
		// TODO 自動生成されたメソッド・スタブ
		jgen.writeString("");

	}

}

JacksonTest.java

package ts0818.jackson.test;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Map;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;

public class JacksonTest {

	public ObjectMapper mapper;

	public JacksonTest() {
		// JacksonのObjectMapper
		mapper = new ObjectMapper();
	    DefaultSerializerProvider.Impl dsp = new DefaultSerializerProvider.Impl();
	    dsp.setNullValueSerializer(new NullValueSerializer());
	    mapper.setSerializerProvider(dsp);

	}

	public static void main(String[] args) {
		// JSONデータ
		String json = "{\r\n    \"id\": \"957c43f2-fa2e-42f9-bf75-6e3d5bb6960a\",\r\n    \"name\": \"The Best Product\",\r\n    \"brand\": {\r\n        \"id\": \"9bcd817d-0141-42e6-8f04-e5aaab0980b6\",\r\n        \"name\": \"Baccarat\",\r\n        \"owner\": {\r\n            \"id\": \"b21a80b1-0c09-4be3-9ebd-ea3653511c13\",\r\n            \"name\": \"Fortune Legend Limited\",\r\n            \"manager\" : {\r\n                \"id\" : \"98jughul-078b-ih09-kju9-p09juhugh086\",\r\n                \"name\" : null,\r\n                \"employee\" : {\r\n                    \"id\" : \"98jughul-078b-ih09-kju9-p09lhrs54676\",\r\n                    \"name\" : null\r\n                }\r\n            }\r\n        }\r\n    }  \r\n}";

		InputStream input = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));

		JacksonTest jacksonTest = new JacksonTest();

		JsonNode rootNode = null;
		try {
			// JSONデータをオブジェクトに変換

			rootNode = jacksonTest.mapper.readValue(input, JsonNode.class);
			System.out.println(rootNode);

			rootNode.forEach(node -> {
				nodeTypeChecker(node);

			});
			System.out.println(rootNode);


		} catch (IOException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}

	}

	public static JsonNode nodeTypeChecker(JsonNode node) {

		switch(node.getNodeType()) {
		  case ARRAY :
			  System.out.println("ARRAY");
		  case OBJECT :
			  System.out.println("OBJECT");
			  System.out.println(node);
			  objectNullCheck(node);
			break;
		  case POJO :
			  System.out.println("POJO");
			break;
		  case BOOLEAN :
			  System.out.println("BOOLEAN");
			break;
		  case BINARY :
			  System.out.println("BINARY");
			break;
		  case MISSING :
			  System.out.println("MISSING");
			break;
		  case NULL :
			  System.out.println("NULL");
			break;
		  case NUMBER :
			  System.out.println("NUMBER");
			break;
		  case STRING :
			  System.out.println("STRING");
			  System.out.println(node);

			break;
		}
		return node;
	}


	public static JsonNode objectNullCheck(JsonNode node) {
		Iterator<Map.Entry<String, JsonNode>> nodeObjectFileds = node.fields();
		Map.Entry<String, JsonNode> entry;
		while(nodeObjectFileds.hasNext()) {
			entry = nodeObjectFileds.next();
			if(entry.getValue().getNodeType() == JsonNodeType.ARRAY || entry.getValue().getNodeType() == JsonNodeType.OBJECT) {
				objectNullCheck(entry.getValue());
			}

		}
		return node;
	}

}

そして、メインメソッドのあるクラス(ここでは、「jacksonTest.java」)を選択した状態で右クリックし、「実行(R)」>「Java アプリケーション」を選択。

f:id:ts0818:20180917192514p:plain

はい、null は変わらずですけど?

f:id:ts0818:20180917192418p:plain

全然できないんですけど...

もしかして、シリアライザーって、JavaオブジェクトからJSONを作るときにしか有効じゃないってこと?

ってなると、もう力技で、JSONデータからnullを検索して置換処理ってぐらいしか方法がなくなるんですが...。

 

ということで、

confrage.jp

⇧  上記サイト様を参考に、無理やりObjectをMapに置換しての荒業になりました...うまい方法が思いつかんですね(涙)。

 というわけで、「JacksonTest02.java」を作成。(「JacksonTest.java」を編集すれば良かったんだけど、新しく作ったほうが早いと思い...)

f:id:ts0818:20180919070843p:plain

 

package ts0818.jackson.test;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonTest02 {

	public static void main(String[] args) {
		// TODO 自動生成されたメソッド・スタブ
		// JSONデータ
		String json = "{\r\n    \"id\": \"957c43f2-fa2e-42f9-bf75-6e3d5bb6960a\",\r\n    \"name\": \"The Best Product\",\r\n    \"brand\": {\r\n        \"id\": \"9bcd817d-0141-42e6-8f04-e5aaab0980b6\",\r\n        \"name\": \"Baccarat\",\r\n        \"owner\": {\r\n            \"id\": \"b21a80b1-0c09-4be3-9ebd-ea3653511c13\",\r\n            \"name\": \"Fortune Legend Limited\",\r\n            \"manager\" : {\r\n                \"id\" : \"98jughul-078b-ih09-kju9-p09juhugh086\",\r\n                \"name\" : null,\r\n                \"employee\" : {\r\n                    \"id\" : \"98jughul-078b-ih09-kju9-p09lhrs54676\",\r\n                    \"name\" : null\r\n                }\r\n            }\r\n        }\r\n    }  \r\n}";

        ObjectMapper mapper = new ObjectMapper();
        
        // LinkedHashMapで順番を保持
        Map<String,Object> map = new LinkedHashMap<>();

        try {
            // JSONから、LinkedHashMap<String, Object>を作って、Map<String, Object>に格納
            map = mapper.readValue(json, new TypeReference<LinkedHashMap<String,Object>>(){});
            System.out.println(map.toString());
            // 再帰処理で、mapの中身を処理
            mapChecker(map);


        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        System.out.println(map.toString());
	}

	public static Map<String, Object> mapChecker(Map<String, Object> targetMap) {
	    // Mapの中身を、key=value の形で取得
        for(Entry<String, Object> set : targetMap.entrySet()) {
            // valueでオブジェクトになってる部分は、LinkedHashMapになってるらしい
        	if(set.getValue() != null && set.getValue().getClass() == LinkedHashMap.class) {
        		System.out.println("Object : " + set.getValue().getClass());
                // オブジェクトが、LinkedHashMapの場合は、同じ処理を(再帰)
        		mapChecker(autoCast(set.getValue()));
        	}


        	if(set.getValue() == null) {
        		//System.out.println(set.getValue());
        		targetMap.put(set.getKey().toString(), "");
        	}
        }

		return targetMap;
	}

    // キャストする用
    @SuppressWarnings("unchecked")
    public static <T> T autoCast(Object obj) {
        T castObj = (T) obj;
        return castObj;
    }

}
    

ということで、「実行(R)」>「Java アプリケーション」を選択。

f:id:ts0818:20180918224603p:plain

nullが置換されました。

f:id:ts0818:20180918223504p:plain

なんとか、NULLを空文字に置換はできたんですが、これ、JavaのClassのオブジェクトにマッピングできるんですかね?

マッピングできなかったら、なんの意味もないですしね...

そして、シリアライザー用のクラスとか結局まったく使わなかったという...

まぁ、今回はNULLチェックということなので、JSONからJavaオブジェクトへのマッピングは時間のあるときに勉強していきたいですね。

私も、先輩社員に教わりたい...そんな気持ちを常に抱いていますかね。

 

今回は、このへんで。 

 

 

番外編

jacksonは、Version1 と version2 は互換性が無いというのを知らず、version1 のものを入れてハマりました。

というわけで、ここからさきは、間違って、JacksonのVersion1系を入れてしまったときの話ですかね。

 

https://mvnrepository.com/artifact/org.codehaus.jackson にアクセスしてライブラリをダウンロード。「Data Mapper For Jackson」をクリック。(Apacheライセンスのほうで大丈夫かと)

f:id:ts0818:20180917164616p:plain

2013年で更新が止まってるのが気にはなるけど...「1.9.13」のリンクをクリック。

f:id:ts0818:20180917164946p:plain

「View All」をクリック。

f:id:ts0818:20180917165201p:plain

「jackson-mapper-asl-1.9.13.jar」をクリック。「保存」で。

f:id:ts0818:20180917165347p:plain

ダウンロードされました。 

f:id:ts0818:20180917165636p:plain

 プロジェクトのライブラリ用のディレクトリに配置します。

f:id:ts0818:20180917165827p:plain

「ファイルをコピー(C)」にチェックされた状態で、「OK」。 

f:id:ts0818:20180917170135p:plain

そしたら、ビルド・パスの構成にライブラリを追加します。 「参照ライブラリ」からも追加できるようです。

f:id:ts0818:20180917170352p:plain

「外部 JAR の追加(X)...」を選択。

f:id:ts0818:20180917170441p:plain

ライブラリを選択し、「開く(O)」で。 

f:id:ts0818:20180917170636p:plain

「適用(A)」をクリック。

f:id:ts0818:20180917170744p:plain

「OK」をクリック。

f:id:ts0818:20180917170918p:plain

参照できるライブラリに追加されました。

f:id:ts0818:20180917171020p:plain