2013-12-03 3 views
9

Имейте в виду, что структура 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); 

Однако полученный экземпляр карты в основном Карта вложенных Maps:

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

В то время как мне нужна плоская карта с Свести ключи с использованием «точечная нотации», как выше.

Я не хотел бы реализовать это сам, хотя в данный момент я не вижу никакого другого пути ...

+0

Jackson (или любая другая библиотека JSON) может преобразовывать JSON в карту карт. Переход на лишнюю милю не является тривиальным, и пример синтаксиса, который вы показываете, никогда не может быть сгенерирован во время выполнения в java. Вы можете добиться чего-то похожего на то, что вам нужно, используя библиотеку [Configafe Config] (https://github.com/typesafehub/config). –

+0

ОК, поэтому я сделал Config parseString = ConfigFactory.parseString (portJsonString); Какой toString() имеет значение: Config (SimpleConfigObject ({«Port»: {«Enabled»: «false», «Number»: «10033», «Type»: «DIAGNOSTIC», «@ alias»: «диагностика "," ExtendedProperties ": {" Свойство ": {" @ name ":" suspend "," $ ":" n "}}," Protocol ":" JDWP "}})) Но я не уверен, как сгладить это через Configafe Config lib? – Svilen

+0

Добавлен ответ. Надеюсь, это поможет. –

ответ

19

Вы можете сделать это, чтобы пересечь дерево и отслеживать, насколько глубоко вы должны выяснить, точечные имена обозначения свойств:

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, 
[email protected]=false, 
Port.Number=10092, 
Port.ExtendedProperties.Property[0][email protected]=connectionTimeout, 
Port.ThreadPool.ThreadPriority=5, 
Port.Protocol=http, 
Port.KeepAliveTimeout=20000, 
Port.ExtendedProperties.Property[0].$=20000, 
[email protected]=defaultHttp, 
Port.Enabled=true 

Должно быть достаточно легко разбить @ и $ в именах свойств, хотя вы можете столкнуться с коллизиями в именах ключей, так как вы сказали, что JSON был произвольным.

+0

В конце дня я пошел с пользовательским решением, очень похожим на это, поэтому отмечая это как принятый ответ. Благодаря! – Svilen

0

Если вы знаете структуру заранее, вы можете определить класс Java и использовать gson для разбора JSON в экземпляр этого класса:

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

Если нет, то я не уверен, что вы пытаетесь сделать. Вы, очевидно, не можете определить класс на лету, поэтому доступ к анализируемому JSON с использованием точечной нотации не может быть и речи.

Если вы не хотите что-то вроде:

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

Если это так, то обхода карты карт и построения «сплющенных» карта не кажется слишком большая проблема.

Или это что-то еще?

+0

Модель не известна заранее. Идея состоит в том, чтобы передать эту сглаженную JSON в службу по продаже, которая, ну, чтобы сравнить ее с другим json, который сглаживается таким же образом. Проблема в том, что служба ожидает определенный формат, то есть эту сглаженную вещь, которую я пытаюсь достичь ... Я думал, что это должна быть не такая необычная проблема, поэтому какое-то решение должно быть доступно некоторым lib. – Svilen

+0

@Svilen Почему вы не можете просто сравнить строки JSON? – siledh

+0

siledh, причина в том, что я хотел бы иметь простой способ изменить только одно значение свойства/листа после того, как был выполнен diff. Мне было бы намного сложнее сделать простой текст diff ... – Svilen

1

Вы можете достичь что-то подобное, используя 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); 
    } 
    } 
} 
+0

Спасибо, Джованни. Хотя это действительно шаг ближе к тому, что мне нужно, этого все еще недостаточно. Например, он создает такие вещи, как: Порт. «@ Alias» = ConfigString («defaultHttp») и Port.ExtendedProperties.Property = SimpleConfigList ([{"@ name": "jaasRealm", "$": "PlatformManagement"}]) - явно не сплющенный набор свойств. Итак, если я хочу использовать это, мне нужно будет дополнительно проанализировать вывод библиотеки Typesafe .... Я думаю, что я просто использую свое собственное решение и опубликую его здесь, когда это будет сделано. – Svilen

+0

Ну, что вы пытаетесь сделать, это очень «обычай», потому что у вас есть имена полей стиля аннотаций, которые представляют что-то другое. Насколько я знаю, концепция не является «стандартным» JSON или другим, поэтому вам нужно будет немного поработать здесь. Я надеюсь, что библиотека «Типы» поможет. –

+1

Вам просто нужно выполнить некоторую обработку, если вы хотите другой формат, а не просто вызвать toString. Каждая запись имеет выражение пути, которое может быть разбито с помощью 'ConfigUtil.splitPath', если вы хотите, и ConfigValue может быть преобразован в простое значение Java с помощью' value.unwrapped() '. Учитывая путь разделения, вы можете собрать его, используя желаемый синтаксис. –

6

как об этом:

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()); 
} 
} 
9

Как об использовании JSON-Flattener.https://github.com/wnameless/json-flattener

BTW, я являюсь автором этой библиотеки.

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

// Result: 
{ 
    "[email protected]":"defaultHttp", 
    "Port.Enabled":"true", 
    "Port.Number":"10092", 
    "Port.Protocol":"http", 
    "Port.KeepAliveTimeout":"20000", 
    "[email protected]":"false", 
    "Port.ThreadPool.Max":"150", 
    "Port.ThreadPool.ThreadPriority":"5", 
    "Port.ExtendedProperties.Property[0][email protected]":"connectionTimeout", 
    "Port.ExtendedProperties.Property[0].$":"20000" 
} 
+0

Это выглядит круто. Мне нужно попробовать. Благодаря! – Svilen

+0

действительно хорошая работа. надеюсь, вы продолжите поддерживать его и готово к производству. хорошо, если вы также можете опубликовать показатели производительности и тестирования покрытия на реестре ur github. Вы все еще активно развиваете его? –

+0

Могу ли я открыть карту json? – PDS

1

org.springframework.integration.transformer.ObjectToMapTransformer от 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(); 

Side Примечание: Это, вероятно, избыточно добавить Spring Integration к классам просто использовать один класс, но вы можете проверить реализацию этого класса и написать аналогичное решение на твой собственный. Вложенная карта создается Jackson (org.springframework.integration.support.json.JsonObjectMapper#fromJson(payload, Map.class)), затем mapis рекурсивно перемещается, сглаживая все значения, которые являются коллекциями.

+1

Последняя строка может быть намного проще: 'Map payload = transformer.doTransform (message);' –

Смежные вопросы