Created
June 25, 2019 09:10
-
-
Save seraphy/9e67565d1d1ae0251aadf034531370a9 to your computer and use it in GitHub Desktop.
データポイントのような大量データを効率よくJSONで扱う実装例。簡略化した文字列でJSON表現とする方法と、リストをバイナリにして単一のbase64文字列にする方法。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package jp.seraphyware.example.java8learn.json; | |
import java.io.ByteArrayInputStream; | |
import java.io.ByteArrayOutputStream; | |
import java.io.DataInputStream; | |
import java.io.DataOutputStream; | |
import java.io.IOException; | |
import java.util.ArrayList; | |
import java.util.Base64; | |
import java.util.List; | |
import java.util.zip.GZIPInputStream; | |
import java.util.zip.GZIPOutputStream; | |
import com.fasterxml.jackson.annotation.JsonValue; | |
import com.fasterxml.jackson.core.JsonGenerator; | |
import com.fasterxml.jackson.core.JsonParser; | |
import com.fasterxml.jackson.core.JsonProcessingException; | |
import com.fasterxml.jackson.databind.DeserializationContext; | |
import com.fasterxml.jackson.databind.JsonDeserializer; | |
import com.fasterxml.jackson.databind.JsonSerializer; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import com.fasterxml.jackson.databind.SerializerProvider; | |
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; | |
import com.fasterxml.jackson.databind.annotation.JsonSerialize; | |
/** | |
* データポイントのような大量データを効率よくJSONで扱うために、 | |
* ・ 簡略化した文字列でオブジェクトを表現する方法 | |
* ・ リストごとバイナリにして表現する方法 | |
* の実装例 | |
*/ | |
public class CustomSerializeExample { | |
/** | |
* シンプルな座標データ | |
*/ | |
public static class DataPoint { | |
double x; | |
double y; | |
boolean flag; | |
public DataPoint() { | |
this(0, 0, false); | |
} | |
public DataPoint(double x, double y) { | |
this(x, y, false); | |
} | |
public DataPoint(double x, double y, boolean flag) { | |
this.x = x; | |
this.y = y; | |
this.flag = flag; | |
} | |
/** | |
* JSONのデシリアライズのために必要な文字列を受け取るコンストラクタ。 | |
* toStringと対となる。 | |
* @param str | |
*/ | |
public DataPoint(String str) { | |
if (str == null || str.length() == 0) { | |
return; | |
} | |
String[] tokens = str.split(","); | |
if (tokens.length != 2 && tokens.length != 3) { | |
throw new IllegalArgumentException("invalid format:" + str); | |
} | |
double x = Double.parseDouble(tokens[0]); | |
double y = Double.parseDouble(tokens[1]); | |
boolean flag; | |
if (tokens.length == 3) { | |
flag = Boolean.parseBoolean(tokens[2]); | |
} else { | |
flag = false; | |
} | |
this.x = x; | |
this.y = y; | |
this.flag = flag; | |
} | |
public double getX() { | |
return x; | |
} | |
public void setX(double x) { | |
this.x = x; | |
} | |
public double getY() { | |
return y; | |
} | |
public void setY(double y) { | |
this.y = y; | |
} | |
public boolean isFlag() { | |
return flag; | |
} | |
public void setFlag(boolean flag) { | |
this.flag = flag; | |
} | |
/** | |
* 文字列化。 | |
* コンパクトなJSONの文字列表現としてフィールドではなく単純な文字列にしておく。 | |
*/ | |
@Override | |
@JsonValue | |
public String toString() { | |
if (!flag) { | |
return x + "," + y; | |
} | |
return x + "," + y + "," + flag; | |
} | |
} | |
/** | |
* 座標データのリストを保持するクラス | |
*/ | |
public static class Foo { | |
/** | |
* カスタマイズしていない素のリストによるデータポイントの保持リスト | |
*/ | |
private final List<DataPoint> dps = new ArrayList<>(); | |
/** | |
* Jacksonのシリアライズ・デシリアライズをカスタマイズしているデータポイントの保持リスト。 | |
* JSONのリストとしてではなく、1つのgzip圧縮されたバイナリのbase64表現にしている。 | |
*/ | |
@JsonSerialize(using = ListDataPointSerializer.class) | |
@JsonDeserialize(using = ListDataPointDeserializer.class) | |
private final List<DataPoint> dps2 = new ArrayList<>(); | |
public List<DataPoint> getDps() { | |
return dps; | |
} | |
public List<DataPoint> getDps2() { | |
return dps2; | |
} | |
@Override | |
public String toString() { | |
return "Foo [dps=" + dps + ", dps2=" + dps2 + "]"; | |
} | |
} | |
/** | |
* DataPointのリストをバイナリ形式のgzip圧縮したbase64文字列として表現する。 | |
*/ | |
private static class ListDataPointSerializer extends JsonSerializer<List<DataPoint>> { | |
@Override | |
public void serialize(List<DataPoint> value, JsonGenerator gen, SerializerProvider serializers) | |
throws IOException { | |
ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |
try (GZIPOutputStream os = new GZIPOutputStream(bos); | |
DataOutputStream dos = new DataOutputStream(os)) { | |
if (value == null) { | |
dos.writeInt(-1); | |
} else { | |
dos.writeInt(value.size()); | |
for (DataPoint dp : value) { | |
dos.writeDouble(dp.getX()); | |
dos.writeDouble(dp.getY()); | |
dos.writeBoolean(dp.isFlag()); | |
} | |
} | |
} | |
gen.writeString(Base64.getEncoder().encodeToString(bos.toByteArray())); | |
} | |
} | |
/** | |
* base64文字列を受け取りgzip圧縮されたDataPointのリストとして復元する | |
*/ | |
private static class ListDataPointDeserializer extends JsonDeserializer<List<DataPoint>> { | |
@Override | |
public List<DataPoint> deserialize(JsonParser p, DeserializationContext ctxt) | |
throws IOException, JsonProcessingException { | |
String text = p.getText(); | |
if (text == null || text.length() == 0) { | |
return null; | |
} | |
byte[] raw = Base64.getDecoder().decode(text); | |
ByteArrayInputStream bis = new ByteArrayInputStream(raw); | |
try (GZIPInputStream is = new GZIPInputStream(bis); | |
DataInputStream dis = new DataInputStream(is)) { | |
int len = dis.readInt(); | |
if (len < 0) { | |
return null; | |
} | |
List<DataPoint> dps = new ArrayList<>(); | |
for (int idx = 0; idx < len; idx++) { | |
double x = dis.readDouble(); | |
double y = dis.readDouble(); | |
boolean flag = dis.readBoolean(); | |
DataPoint dp = new DataPoint(x, y, flag); | |
dps.add(dp); | |
} | |
return dps; | |
} | |
} | |
} | |
/** | |
* 実験コード | |
* @param args | |
* @throws Exception | |
*/ | |
public static void main(String[] args) throws Exception { | |
// データの作成 | |
Foo foo = new Foo(); | |
for (int i = 0; i < 10; i++) { | |
DataPoint dp = new DataPoint(); | |
dp.setX(i); | |
dp.setY(2000 + i); | |
dp.setFlag(i % 3 == 0); | |
foo.getDps().add(dp); | |
foo.getDps2().add(dp); | |
} | |
// JSONへのシリアライズ | |
ObjectMapper mapper = new ObjectMapper(); | |
String json = mapper.writeValueAsString(foo); | |
System.out.println("json=" + json); | |
// 復元 | |
Foo foo2 = mapper.readValue(json, Foo.class); | |
System.out.println("foo2=" + foo2); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
json={"dps":["0.0,2000.0,true","1.0,2001.0","2.0,2002.0","3.0,2003.0,true","4.0,2004.0","5.0,2005.0","6.0,2006.0,true","7.0,2007.0","8.0,2008.0","9.0,2009.0,true"],"dps2":"H4sIAAAAAAAAAGNgYOBigAKH+Q5gmtH+A0zABcqAq/CAMjhgAj4QLQ4CMIEAKEMEJhACZUjABCKgWmRgAjFQhgJMIAHKUIIJpEC0AACGoDNyrgAAAA=="} | |
foo2=Foo [dps=[0.0,2000.0,true, 1.0,2001.0, 2.0,2002.0, 3.0,2003.0,true, 4.0,2004.0, 5.0,2005.0, 6.0,2006.0,true, 7.0,2007.0, 8.0,2008.0, 9.0,2009.0,true], dps2=[0.0,2000.0,true, 1.0,2001.0, 2.0,2002.0, 3.0,2003.0,true, 4.0,2004.0, 5.0,2005.0, 6.0,2006.0,true, 7.0,2007.0, 8.0,2008.0, 9.0,2009.0,true]] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment