DynamoDBくん「KVSだから何でも入れられるよ！」 Javaくん「Java使ってるんだからエンティティの構造しっかり決まってるんでしょ？」

___FXXXXXXXXXCK!!!___ 

## DynamoDBに入ってるJsonオブジェクトをJavaで扱えるようにしたいだけなのに

これが調べても調べても「ちゃんとエンティティ用意すればマッピングしてくれるよ！」って方法しか出てこないんですよ。それDynamoじゃなくてよくね？？何のためにKVS使ってると思ってるのさ。

## Typeを調べる方法すらない

DynamoDBから引っぱってきたデータは `AttributeValue` (正確には `Map<String, AttributeValue>`) という型に入っているんですが、 [Javadoc](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html) を見るとわかる通り ___値の型を調べる方法が無い___ んですよ。こいつを直接 `toString` してあげるとDynamoDBでおなじみの `N` とか `S` とか入ってるのが見えるんですけど、 ___なぜかそいつを参照できるメソッドが無い___ という始末。
(しかも少し前のバージョンでは `hasXs()` 系のメソッドや `hasM()` すら無かったからリストがなんのリストなのか直接調べる方法がマジで無かったし `Map` だった場合は本当にどうしようもなかった)
一応 `s()` とか `n()` とかで規定外の型を取り出そうとすると `null` が返ってくるので、虱潰しに調べるという方法も無くはなかったんですが、 ___`l()` が無条件で空リストを返してくる___ というとんでもない仕様だったのでなんかもうなんかもう


## とりあえず書き殴ったコード

目下必要なところだけ書いたけどたりないものだらけだからマジで公式で出して……ほんと……

```java
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import software.amazon.awssdk.services.dynamodb.model.AttributeValue;

public class DynamoDbUtil {

  private class Type{
    static final String nameString = "string";
    static final String nameNumeric = "numeric";
    static final String nameBoolean = "boolean";
    static final String nameArray = "array";
    static final String nameMap = "map";
  }

  static public Boolean isString (AttributeValue a) {
    return (a.s() != null);
  }

  static public Boolean isNumeric (AttributeValue a) {
    return (a.n() != null);
  }

  static public Boolean isBoolean (AttributeValue a) {
    return (a.bool() != null);
  }

  static public Boolean isBinary (AttributeValue a) {
    return (a.b() != null);
  }

  static public Boolean isNumericList (AttributeValue a) {
    return a.hasNs();
  }

  static public Boolean isStringList (AttributeValue a) {
    return a.hasSs();
  }

  static public Boolean isAnyList (AttributeValue a) {
    return a.hasL();
  }

  static public Boolean isMap (AttributeValue a) {
    return a.hasM();
  }

  static public List<Double> toNumberList (List<AttributeValue> as){
    if(as.get(0).n() == null){
      throw new RuntimeException("its not number");
    }
    List<Double> ds = as.stream().map(a -> Double.parseDouble(a.n())).collect(Collectors.toList());
    return ds;
  }

  static public List<String> toStringList(List<AttributeValue> as){
    if(as.get(0).s() == null){
      throw new RuntimeException("its not string");
    }
    List<String> ss = as.stream().map(a -> a.s()).collect(Collectors.toList());
    return ss;
  }

  static public String toString (AttributeValue a) {
    if(isString(a)){
      return a.s();
    }
    if(isNumeric(a)){
      return a.n();
    }
    if(isBoolean(a)){
      return Boolean.toString(a.bool());
    }
    if(isNumericList(a)){
      return toNumberList(a.l()).toString();
    }
    if (isStringList(a)){
      return toStringList(a.l()).toString();
    }
    throw new RuntimeException("inviled value type");
  }

  static public String type(AttributeValue a){
    if(isNumericList(a) || isStringList(a) || isAnyList(a)){
      return Type.nameArray;
    }
    if(isBoolean(a)){
      return Type.nameBoolean;
    }
    if(isNumeric(a)){
      return Type.nameNumeric;
    }
    if(isString(a)){
      return Type.nameString;
    }
    if(isMap(a)){
      return Type.nameMap;
    }
    throw new RuntimeException("inviled value type");
  }

  static public JsonNode toJsonNode(List<Map<String, AttributeValue>> is){
    ObjectMapper mapper = new ObjectMapper();
    ArrayNode array = mapper.createArrayNode();
    is.forEach((i) ->{
      array.add(toJsonNode(i));
    });
    return array;
  }

  static public JsonNode toJsonNode(Map<String, AttributeValue> i){
    ObjectMapper mapper = new ObjectMapper();
    ObjectNode node = mapper.createObjectNode();
    i.forEach((k, a) -> {
      switch(type(a)){
        case Type.nameArray:
          node.set(k, toJsonNode_AS(a.l()));
          break;
        case Type.nameBoolean:
          node.put(k, a.bool());
          break;
        case Type.nameNumeric:
          node.put(k, Integer.parseInt(a.n()));
          break;
        case Type.nameMap:
          node.set(k, toJsonNode(a.m()));
        default:
          node.put(k, a.s());
      }
    });

    return node;
  }

  static public JsonNode toJsonNode_AS(List<AttributeValue> as){
    ObjectMapper mapper = new ObjectMapper();
    ArrayNode array = mapper.createArrayNode();
    as.forEach((a) -> {
      logger.debug(type(a));
      switch(type(a)){
        case Type.nameArray:
          array.add(toJsonNode_AS(a.l()));
          break;
        case Type.nameBoolean:
          array.add(a.bool());
          break;
        case Type.nameNumeric:
          array.add(Integer.parseInt(a.n()));
          break;
        case Type.nameMap:
          array.add(toJsonNode(a.m())); 
        default:
          array.add(a.s());
      }
    });
    return array;
  }
}

```