Author: dleangen Date: Fri Aug 25 11:22:45 2017 New Revision: 1806158 URL: http://svn.apache.org/viewvc?rev=1806158&view=rev Log: [FELIX-5412] Added ability to determine the order of the keys to help with readability and debugging
Modified: felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/DebugJsonWriter.java felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/DefaultJsonWriter.java felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/JsonWriterFactory.java felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/yaml/YamlWriterFactory.java felix/trunk/converter/serializer/src/main/java/org/osgi/service/serializer/WriterFactory.java felix/trunk/converter/serializer/src/test/java/org/apache/felix/serializer/impl/json/JsonBackingObjectSerializationTest.java Modified: felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/DebugJsonWriter.java URL: http://svn.apache.org/viewvc/felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/DebugJsonWriter.java?rev=1806158&r1=1806157&r2=1806158&view=diff ============================================================================== --- felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/DebugJsonWriter.java (original) +++ felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/DebugJsonWriter.java Fri Aug 25 11:22:45 2017 @@ -19,6 +19,8 @@ package org.apache.felix.serializer.impl import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -31,21 +33,23 @@ import org.osgi.util.converter.Converter public class DebugJsonWriter implements Writer { - private final Converter converter; + private Converter converter; + private final Map<String, List<String>> orderingRules; private final boolean ignoreNull = false; private final int indentation = 2; - public DebugJsonWriter(Converter c) { + public DebugJsonWriter(Converter c, Map<String,List<String>> rules) { converter = c; + orderingRules = rules; } @Override public String write(Object obj) { - return encode(obj, 0).trim(); + return encode(obj, "/", 0).trim(); } @SuppressWarnings("rawtypes") - private String encode(Object obj, int level) { + private String encode(Object obj, String path, int level) { if (obj == null) { return ignoreNull ? "" : "null"; } @@ -53,13 +57,14 @@ public class DebugJsonWriter implements if (obj instanceof String) { return "\"" + (String)obj + "\""; } else if (obj instanceof Map) { - return encodeMap((Map) obj, level); + return encodeMap(orderMap((Map)obj, path), path, level); } else if (obj instanceof Collection) { - return encodeCollection((Collection) obj, level); + return encodeCollection((Collection) obj, path, level); } else if (obj instanceof DTO) { - return encodeMap(converter.convert(obj).sourceAsDTO().to(Map.class), level); + Map converted = converter.convert(obj).sourceAsDTO().to(Map.class); + return encodeMap(orderMap(converted, path), path, level); } else if (obj.getClass().isArray()) { - return encodeCollection(asCollection(obj), level); + return encodeCollection(asCollection(obj), path, level); } else if (obj instanceof Number) { return obj.toString(); } else if (obj instanceof Boolean) { @@ -69,6 +74,40 @@ public class DebugJsonWriter implements return "\"" + converter.convert(obj).to(String.class) + "\""; } + @SuppressWarnings( { "unchecked", "rawtypes" } ) + private Map orderMap(Map unordered, String path) { + Map ordered = (orderingRules.containsKey(path)) ? new LinkedHashMap<>() : new TreeMap<>(); + List<String> keys = (orderingRules.containsKey(path)) ? orderingRules.get(path) : new ArrayList<>(unordered.keySet()); + for (String key : keys) { + String itemPath = (path.endsWith("/")) ? path + key : path + "/" + key; + Object value = unordered.get(key); + if (value instanceof Map) + ordered.put(key, orderMap((Map)value, itemPath)); + else if(value instanceof Collection) + ordered.put(key, orderCollectionItems((Collection)value, itemPath)); + else + ordered.put(key, value); + } + + return ordered; + } + + @SuppressWarnings( { "unchecked", "rawtypes" } ) + private List orderCollectionItems(Collection unordered, String path) { + List ordered = new ArrayList<>(); + for (Object obj: unordered) { + if (obj instanceof Map) + ordered.add(orderMap((Map)obj, path)); + else if(obj instanceof Collection) + ordered.add(orderCollectionItems((Collection)obj, path)); + else + ordered.add(obj); + } + + try{Collections.sort(ordered);}catch (Exception e){} + return ordered; + } + private Collection<?> asCollection(Object arr) { // Arrays.asList() doesn't work for primitive arrays int len = Array.getLength(arr); @@ -79,7 +118,7 @@ public class DebugJsonWriter implements return l; } - private String encodeCollection(Collection<?> collection, int level) { + private String encodeCollection(Collection<?> collection, String path, int level) { level++; StringBuilder sb = new StringBuilder("[\n"); @@ -90,8 +129,8 @@ public class DebugJsonWriter implements else sb.append(",\n"); - sb.append( getIdentPrefix(level)); - sb.append(encode(o, level)); + sb.append(getIdentPrefix(level)); + sb.append(encode(o, path, level)); } sb.append("\n"); @@ -101,22 +140,23 @@ public class DebugJsonWriter implements } @SuppressWarnings({ "rawtypes", "unchecked" }) - private String encodeMap(Map m, int level) { + private String encodeMap(Map m, String path, int level) { level++; - Map orderedMap = new TreeMap<>(m); +// Map orderedMap = (orderingRules.isEmpty()) ? new TreeMap<>(m) : m; StringBuilder sb = new StringBuilder("{\n"); - for (Entry entry : (Set<Entry>) orderedMap.entrySet()) { + for (Entry entry : (Set<Entry>) m.entrySet()) { if (entry.getKey() == null || entry.getValue() == null) if (ignoreNull) continue; + String itemPath = (path.endsWith("/")) ? path + entry.getKey() : path + "/" + entry.getKey(); if (sb.length() > 2) sb.append(",\n"); sb.append(getIdentPrefix(level)); sb.append('"'); sb.append(entry.getKey().toString()); sb.append("\":"); - sb.append(encode(entry.getValue(), level)); + sb.append(encode(entry.getValue(), itemPath, level)); } sb.append("\n"); sb.append(getIdentPrefix(--level)); Modified: felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/DefaultJsonWriter.java URL: http://svn.apache.org/viewvc/felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/DefaultJsonWriter.java?rev=1806158&r1=1806157&r2=1806158&view=diff ============================================================================== --- felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/DefaultJsonWriter.java (original) +++ felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/DefaultJsonWriter.java Fri Aug 25 11:22:45 2017 @@ -49,11 +49,6 @@ public class DefaultJsonWriter implement } if (obj instanceof String) { - // Optimization for when the value is already a String - // David B.: is this ok? Or does the Converter do something else - // other than just returning a String?? - // I noticed that a lot of calculations were going on, just - // to return a String anyway. return "\"" + (String)obj + "\""; } else if (obj instanceof Map) { return encodeMap((Map) obj); Modified: felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/JsonWriterFactory.java URL: http://svn.apache.org/viewvc/felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/JsonWriterFactory.java?rev=1806158&r1=1806157&r2=1806158&view=diff ============================================================================== --- felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/JsonWriterFactory.java (original) +++ felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/json/JsonWriterFactory.java Fri Aug 25 11:22:45 2017 @@ -16,11 +16,22 @@ */ package org.apache.felix.serializer.impl.json; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.osgi.service.serializer.Writer; import org.osgi.service.serializer.WriterFactory; import org.osgi.util.converter.Converter; -public class JsonWriterFactory implements WriterFactory { +public class JsonWriterFactory implements WriterFactory, WriterFactory.JsonWriterFactory { + private final Map<String, List<String>> orderingRules = new HashMap<>(); + + @Override + public JsonWriterFactory orderBy(String path, List<String> keyOrder) { + orderingRules.put(path, keyOrder); + return this; + } @Override public Writer newDefaultWriter(Converter c) { @@ -29,6 +40,6 @@ public class JsonWriterFactory implement @Override public Writer newDebugWriter(Converter c) { - return new DebugJsonWriter(c); + return new DebugJsonWriter(c, orderingRules); } } Modified: felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/yaml/YamlWriterFactory.java URL: http://svn.apache.org/viewvc/felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/yaml/YamlWriterFactory.java?rev=1806158&r1=1806157&r2=1806158&view=diff ============================================================================== --- felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/yaml/YamlWriterFactory.java (original) +++ felix/trunk/converter/serializer/src/main/java/org/apache/felix/serializer/impl/yaml/YamlWriterFactory.java Fri Aug 25 11:22:45 2017 @@ -16,11 +16,22 @@ */ package org.apache.felix.serializer.impl.yaml; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.osgi.service.serializer.Writer; import org.osgi.service.serializer.WriterFactory; import org.osgi.util.converter.Converter; -public class YamlWriterFactory implements WriterFactory { +public class YamlWriterFactory implements WriterFactory, WriterFactory.YamlWriterFactory { + private final Map<String, List<String>> orderingRules = new HashMap<>(); + + @Override + public YamlWriterFactory orderBy(String path, List<String> keyOrder) { + orderingRules.put(path,keyOrder); + return this; + } @Override public Writer newDefaultWriter(Converter c) { Modified: felix/trunk/converter/serializer/src/main/java/org/osgi/service/serializer/WriterFactory.java URL: http://svn.apache.org/viewvc/felix/trunk/converter/serializer/src/main/java/org/osgi/service/serializer/WriterFactory.java?rev=1806158&r1=1806157&r2=1806158&view=diff ============================================================================== --- felix/trunk/converter/serializer/src/main/java/org/osgi/service/serializer/WriterFactory.java (original) +++ felix/trunk/converter/serializer/src/main/java/org/osgi/service/serializer/WriterFactory.java Fri Aug 25 11:22:45 2017 @@ -15,6 +15,8 @@ */ package org.osgi.service.serializer; +import java.util.List; + import org.osgi.annotation.versioning.ProviderType; import org.osgi.util.converter.Converter; @@ -44,6 +46,22 @@ public interface WriterFactory { Writer newDebugWriter(Converter c); /** + * Register an ordering rule for this writer. + * + * An ordering rule causes the written json to be output in the order + * specified. This can be useful, for example, for debugging or when + * the data otherwise needs to be human consumable. + * + * Note that only the target type is specified, so the rule will be visited + * for every conversion to the target type. + * + * @param path the path where the key is located in the object graph. + * @param func The desired key order. + * @return This factory object to allow further invocations on it. + */ + WriterFactory orderBy(String path, List<String> keyOrder); + + /** * A convenience means of obtaining a JsonWriterFactory without having to * configure service settings. */ Modified: felix/trunk/converter/serializer/src/test/java/org/apache/felix/serializer/impl/json/JsonBackingObjectSerializationTest.java URL: http://svn.apache.org/viewvc/felix/trunk/converter/serializer/src/test/java/org/apache/felix/serializer/impl/json/JsonBackingObjectSerializationTest.java?rev=1806158&r1=1806157&r2=1806158&view=diff ============================================================================== --- felix/trunk/converter/serializer/src/test/java/org/apache/felix/serializer/impl/json/JsonBackingObjectSerializationTest.java (original) +++ felix/trunk/converter/serializer/src/test/java/org/apache/felix/serializer/impl/json/JsonBackingObjectSerializationTest.java Fri Aug 25 11:22:45 2017 @@ -17,6 +17,7 @@ package org.apache.felix.serializer.impl.json; import java.lang.reflect.Type; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -74,6 +75,19 @@ public class JsonBackingObjectSerializat assertEquals(EXPECTED, actual); } + @Test + public void testOrderedSerialization() { + final JsonWriterFactory factory = new JsonWriterFactory(); + factory.orderBy("/", Arrays.asList("b", "a", "o", "l2", "l1")); + factory.orderBy("/l2", Arrays.asList("b", "a")); + final String actual = new JsonSerializerImpl() + .serialize(MyDTOishObject.factory("A", "B")) + .writeWith(factory.newDebugWriter(Converters.standardConverter())) + .toString(); + + assertEquals(ORDERED, actual); + } + public static class MyDTOishObject extends DTO { public String a; public String b; @@ -138,10 +152,10 @@ public class JsonBackingObjectSerializat " \"a\":\"A\",\n" + " \"b\":\"B\",\n" + " \"l1\":[\n" + + " \"four\",\n" + " \"one\",\n" + - " \"two\",\n" + " \"three\",\n" + - " \"four\"\n" + + " \"two\"\n" + " ],\n" + " \"l2\":[\n" + " {\n" + @@ -158,4 +172,30 @@ public class JsonBackingObjectSerializat " \"b\":\"BB\"\n" + " }\n" + "}"; + + private static final String ORDERED = + "{\n" + + " \"b\":\"B\",\n" + + " \"a\":\"A\",\n" + + " \"o\":{\n" + + " \"a\":\"AA\",\n" + + " \"b\":\"BB\"\n" + + " },\n" + + " \"l2\":[\n" + + " {\n" + + " \"b\":\"B\",\n" + + " \"a\":\"A\"\n" + + " },\n" + + " {\n" + + " \"b\":\"BB\",\n" + + " \"a\":\"AA\"\n" + + " }\n" + + " ],\n" + + " \"l1\":[\n" + + " \"four\",\n" + + " \"one\",\n" + + " \"three\",\n" + + " \"two\"\n" + + " ]\n" + + "}"; }