web-dev-qa-db-ja.com

JSONをマップのようなフラットな構造に逆シリアル化する方法は?

JSON構造は事前にはわかっていない、つまり完全に任意であることに注意してください。JSON形式であることだけがわかっています。

例えば、

次のJSON

{
   "Port":
   {
       "@alias": "defaultHttp",
       "Enabled": "true",
       "Number": "10092",
       "Protocol": "http",
       "KeepAliveTimeout": "20000",
       "ThreadPool":
       {
           "@enabled": "false",
           "Max": "150",
           "ThreadPriority": "5"
       },
       "ExtendedProperties":
       {
           "Property":
           [                         
               {
                   "@name": "connectionTimeout",
                   "$": "20000"
               }
           ]
       }
   }
}

次のようなキーを持つMapのような構造に逆シリアル化する必要があります(簡潔にするために、上記のすべてが含まれているわけではありません)。

port[0].alias
port[0].enabled
port[0].extendedProperties.connectionTimeout
port[0].threadPool.max

現在ジャクソンを調べているので、

TypeReference<HashMap<String, Object>> typeRef = new TypeReference<HashMap<String, Object>>() {};
Map<String, String> o = objectMapper.readValue(jsonString, typeRef);

ただし、結果のMapインスタンスは基本的にネストされたマップのマップです。

{Port={@alias=diagnostics, Enabled=false, Type=DIAGNOSTIC, Number=10033, Protocol=JDWP, ExtendedProperties={Property={@name=suspend, $=n}}}}

上記のように、「ドット表記」を使用してキーをフラット化したフラットマップが必要です。

現時点では他の方法は見ていませんが、自分で実装するのは避けたいです...

18
Svilen

これを行うと、ツリーを走査して、ドット表記のプロパティ名を理解する深さを追跡できます。

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 com.fasterxml.jackson.databind.node.ValueNode;
import Java.io.IOException;
import Java.util.HashMap;
import Java.util.Iterator;
import Java.util.Map;
import org.junit.Test;

public class FlattenJson {
  String json = "{\n" +
      "   \"Port\":\n" +
      "   {\n" +
      "       \"@alias\": \"defaultHttp\",\n" +
      "       \"Enabled\": \"true\",\n" +
      "       \"Number\": \"10092\",\n" +
      "       \"Protocol\": \"http\",\n" +
      "       \"KeepAliveTimeout\": \"20000\",\n" +
      "       \"ThreadPool\":\n" +
      "       {\n" +
      "           \"@enabled\": \"false\",\n" +
      "           \"Max\": \"150\",\n" +
      "           \"ThreadPriority\": \"5\"\n" +
      "       },\n" +
      "       \"ExtendedProperties\":\n" +
      "       {\n" +
      "           \"Property\":\n" +
      "           [                         \n" +
      "               {\n" +
      "                   \"@name\": \"connectionTimeout\",\n" +
      "                   \"$\": \"20000\"\n" +
      "               }\n" +
      "           ]\n" +
      "       }\n" +
      "   }\n" +
      "}";

  @Test
  public void testCreatingKeyValues() {
    Map<String, String> map = new HashMap<String, String>();
    try {
      addKeys("", new ObjectMapper().readTree(json), map);
    } catch (IOException e) {
      e.printStackTrace();
    }
    System.out.println(map);
  }

  private void addKeys(String currentPath, JsonNode jsonNode, Map<String, String> map) {
    if (jsonNode.isObject()) {
      ObjectNode objectNode = (ObjectNode) jsonNode;
      Iterator<Map.Entry<String, JsonNode>> iter = objectNode.fields();
      String pathPrefix = currentPath.isEmpty() ? "" : currentPath + ".";

      while (iter.hasNext()) {
        Map.Entry<String, JsonNode> entry = iter.next();
        addKeys(pathPrefix + entry.getKey(), entry.getValue(), map);
      }
    } else if (jsonNode.isArray()) {
      ArrayNode arrayNode = (ArrayNode) jsonNode;
      for (int i = 0; i < arrayNode.size(); i++) {
        addKeys(currentPath + "[" + i + "]", arrayNode.get(i), map);
      }
    } else if (jsonNode.isValueNode()) {
      ValueNode valueNode = (ValueNode) jsonNode;
      map.put(currentPath, valueNode.asText());
    }
  }
}

次のマップを生成します。

Port.ThreadPool.Max=150, 
Port.ThreadPool.@enabled=false, 
Port.Number=10092, 
Port.ExtendedProperties.Property[0].@name=connectionTimeout, 
Port.ThreadPool.ThreadPriority=5, 
Port.Protocol=http, 
Port.KeepAliveTimeout=20000, 
Port.ExtendedProperties.Property[0].$=20000, 
Port.@alias=defaultHttp, 
Port.Enabled=true

簡単に取り除くことができます@および$はプロパティ名に含まれますが、JSONは任意であると言ったため、キー名が衝突する可能性があります。

37
Harleen

Json-flattenerの使用についてはどうですか。 https://github.com/wnameless/json-flattener

ところで、私はこのlibの作者です。

String flattenedJson = JsonFlattener.flatten(yourJson);
Map<String, Object> flattenedJsonMap = JsonFlattener.flattenAsMap(yourJson);

// Result:
{
    "Port.@alias":"defaultHttp",
    "Port.Enabled":"true",
    "Port.Number":"10092",
    "Port.Protocol":"http",
    "Port.KeepAliveTimeout":"20000",
    "Port.ThreadPool.@enabled":"false",
    "Port.ThreadPool.Max":"150",
    "Port.ThreadPool.ThreadPriority":"5",
    "Port.ExtendedProperties.Property[0].@name":"connectionTimeout",
    "Port.ExtendedProperties.Property[0].$":"20000"
}
19
user3360932

どのようにそのことについて:

import Java.util.ArrayList;
import Java.util.HashMap;
import Java.util.Map;
import Java.util.Map.Entry;
import com.google.gson.Gson;

/**
 * NOT FOR CONCURENT USE
*/
@SuppressWarnings("unchecked")
public class JsonParser{

Gson gson=new Gson();
Map<String, String> flatmap = new HashMap<String, String>();

public Map<String, String> parse(String value) {        
    iterableCrawl("", null, (gson.fromJson(value, flatmap.getClass())).entrySet());     
    return flatmap; 
}

private <T> void iterableCrawl(String prefix, String suffix, Iterable<T> iterable) {
    int key = 0;
    for (T t : iterable) {
        if (suffix!=null)
            crawl(t, prefix+(key++)+suffix);
        else
            crawl(((Entry<String, Object>) t).getValue(), prefix+((Entry<String, Object>) t).getKey());
    }
}

private void crawl(Object object, String key) {
    if (object instanceof ArrayList)
        iterableCrawl(key+"[", "]", (ArrayList<Object>)object);
    else if (object instanceof Map)
        iterableCrawl(key+".", null, ((Map<String, Object>)object).entrySet());
    else
        flatmap.put(key, object.toString());
}
}
7
Amnons

org.springframework.integration.transformer.ObjectToMapTransformer from Spring Integration は望ましい結果を生成します。デフォルトでは、shouldFlattenKeysプロパティがtrueに設定され、フラットマップが生成されます(ネストなし、値は常に単純型です)。 _shouldFlattenKeys=false_の場合、ネストされたマップが生成されます

ObjectToMapTransformerは、統合フローの一部として使用することを目的としていますが、スタンドアロンで使用することもできます。変換入力のペイロードを使用して_org.springframework.messaging.Message_を構築する必要があります。 transformメソッドは、マップであるペイロードを持つ_org.springframework.messaging.Message_オブジェクトを返します

_import org.springframework.integration.transformer.ObjectToMapTransformer;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;

Message message = new GenericMessage(value);
 ObjectToMapTransformer transformer = new ObjectToMapTransformer();
        transformer.setShouldFlattenKeys(true);
        Map<String,Object> payload = (Map<String, Object>) transformer
                .transform(message)
                .getPayload();
_

補足:単一のクラスを使用するためだけにクラスパスにSpring Integrationを追加するのはおそらくやり過ぎですが、このクラスの実装を確認して、独自のソリューションを作成することもできます。ネストされたマップは、Jackson(org.springframework.integration.support.json.JsonObjectMapper#fromJson(payload, Map.class))によって生成され、その後、mapは再帰的にトラバースされ、コレクションであるすべての値をフラット化します。

1
Bartosz Bilicki

次の例のように、 Typesafe Config Library を使用して、そのようなことを実現できます。

import com.typesafe.config.*;
import Java.util.Map;
public class TypesafeConfigExample {
  public static void main(String[] args) {
    Config cfg = ConfigFactory.parseString(
      "   \"Port\":\n" +
      "   {\n" +
      "       \"@alias\": \"defaultHttp\",\n" +
      "       \"Enabled\": \"true\",\n" +
      "       \"Number\": \"10092\",\n" +
      "       \"Protocol\": \"http\",\n" +
      "       \"KeepAliveTimeout\": \"20000\",\n" +
      "       \"ThreadPool\":\n" +
      "       {\n" +
      "           \"@enabled\": \"false\",\n" +
      "           \"Max\": \"150\",\n" +
      "           \"ThreadPriority\": \"5\"\n" +
      "       },\n" +
      "       \"ExtendedProperties\":\n" +
      "       {\n" +
      "           \"Property\":\n" +
      "           [                         \n" +
      "               {\n" +
      "                   \"@name\": \"connectionTimeout\",\n" +
      "                   \"$\": \"20000\"\n" +
      "               }\n" +
      "           ]\n" +
      "       }\n" +
      "   }\n" +
      "}");

    // each key has a similar form to what you need
    for (Map.Entry<String, ConfigValue> e : cfg.entrySet()) {
      System.out.println(e);
    }
  }
}
1
Giovanni Botta

構造が事前にわかっている場合は、Javaクラスを定義し、 gson を使用してJSONを解析してそのクラスのインスタンスにすることができます。

YourClass obj = gson.fromJson(json, YourClass.class); 

そうでない場合、私はあなたが何をしようとしているのかわかりません。オンザフライでクラスを定義することはできないため、ドット表記を使用して解析済みのJSONにアクセスすることは問題外です。

あなたが好きなものを除いて:

Map<String, String> parsed = magicParse(json);
parsed["Port.ThreadPool.max"]; // returns 150

その場合、マップのマップをトラバースし、「フラット化された」マップを構築することは、それほど問題に見えません。

それとも別のものですか?

0
siledh