diff --git a/src/main/java/org/extendj/ragdoc/JsonBuilder.java b/src/main/java/org/extendj/ragdoc/JsonBuilder.java
index 3b56ce98287ed8e15c4b456d917e67900743329b..b4db22c6eb38ca354c83edd7721e1e9b50348887 100644
--- a/src/main/java/org/extendj/ragdoc/JsonBuilder.java
+++ b/src/main/java/org/extendj/ragdoc/JsonBuilder.java
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2017, Jesper Öqvist <jesper.oqvist@cs.lth.se>
+/* Copyright (c) 2013-2018, Jesper Öqvist <jesper.oqvist@cs.lth.se>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -42,6 +42,7 @@ import org.extendj.ast.ParameterDeclaration;
 import org.extendj.ast.TypeDecl;
 import org.extendj.ast.Variable;
 import org.extendj.util.RelativePath;
+import org.extendj.util.Sorting;
 import se.llbit.json.Json;
 import se.llbit.json.JsonArray;
 import se.llbit.json.JsonObject;
@@ -50,14 +51,12 @@ import se.llbit.json.JsonValue;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeMap;
 
 public class JsonBuilder {
   private Map<String, String> typeIndex = new HashMap<>();
@@ -463,19 +462,31 @@ public class JsonBuilder {
         if (!methodArray.isEmpty()) {
           JsonObject inherited = new JsonObject();
           inherited.add("superclass", typeRef(superclass));
-          inherited.add("members", methodArray);
+          JsonArray sorted = new JsonArray();
+          for (JsonValue member : Sorting.sortBy(methodArray, Sorting.jsonStringFun)) {
+            sorted.add(member);
+          }
+          inherited.add("members", sorted);
           inheritedMethods.add(inherited);
         }
         if (!attributeArray.isEmpty()) {
           JsonObject inherited = new JsonObject();
           inherited.add("superclass", typeRef(superclass));
-          inherited.add("members", attributeArray);
+          JsonArray sorted = new JsonArray();
+          for (JsonValue member : Sorting.sortBy(attributeArray, Sorting.jsonStringFun)) {
+            sorted.add(member);
+          }
+          inherited.add("members", sorted);
           inheritedAttributes.add(inherited);
         }
         if (!fieldArray.isEmpty()) {
           JsonObject inherited = new JsonObject();
           inherited.add("superclass", typeRef(superclass));
-          inherited.add("members", fieldArray);
+          JsonArray sorted = new JsonArray();
+          for (JsonValue member : Sorting.sortBy(fieldArray, Sorting.jsonStringFun)) {
+            sorted.add(member);
+          }
+          inherited.add("members", sorted);
           inheritedFields.add(inherited);
         }
         klass = superclass;
@@ -508,12 +519,7 @@ public class JsonBuilder {
         JsonObject group = new JsonObject();
         group.add("kind", Json.of(kind));
         JsonArray sortedMembers = new JsonArray();
-        KeyFun<JsonValue, String> keyFun = new KeyFun<JsonValue, String>() {
-          @Override public String apply(JsonValue value) {
-            return value.object().get("name").asString("");
-          }
-        };
-        for (JsonValue v : sortBy(groupMap.get(kind), keyFun)) {
+        for (JsonValue v : Sorting.sortBy(groupMap.get(kind), Sorting.objectNameFun)) {
           sortedMembers.add(v);
         }
         group.add("members", sortedMembers);
@@ -549,12 +555,7 @@ public class JsonBuilder {
       packageIndex.add(entry);
     }
     JsonArray sorted = new JsonArray();
-    KeyFun<JsonObject, String> keyFun = new KeyFun<JsonObject, String>() {
-      @Override public String apply(JsonObject value) {
-        return value.object().get("name").asString("");
-      }
-    };
-    for (JsonValue v : sortBy(packageIndex, keyFun)) {
+    for (JsonValue v : Sorting.sortBy(packageIndex, Sorting.objectNameKeyFun)) {
       sorted.add(v);
     }
     return sorted;
@@ -563,47 +564,11 @@ public class JsonBuilder {
   public JsonArray subtypesJson(TypeDecl type) {
     JsonArray subtypes = new JsonArray();
     if (subtypeMap.containsKey(type)) {
-      KeyFun<TypeDecl, String> keyFun = new KeyFun<TypeDecl, String>() {
-        @Override public String apply(TypeDecl type) {
-          return type.name();
-        }
-      };
-      for (TypeDecl subtype : sortBy(subtypeMap.get(type), keyFun)) {
+      for (TypeDecl subtype : Sorting.sortBy(subtypeMap.get(type), Sorting.typeNameFun)) {
         subtypes.add(typeRef(subtype));
       }
     }
     return subtypes;
   }
 
-  public interface KeyFun<T, R> {
-    R apply(T t);
-  }
-
-  /**
-   * Returns a sorted JSON array based on the argument elements and a key function.
-   *
-   * <p>The result preserves duplicate elements and elements with identical keys.</p>
-   */
-  static <T, K extends Comparable<? super K>> List<T> sortBy(
-      Iterable<T> iterable,
-      KeyFun<T, K> keyFun) {
-    Map<K, Collection<T>> members = new TreeMap<>();
-    for (T value : iterable) {
-      K sortKey = keyFun.apply(value);
-      Collection<T> items = members.get(sortKey);
-      if (items == null) {
-        items = new ArrayList<>();
-        members.put(sortKey, items);
-      }
-      items.add(value);
-    }
-    List<K> keys = new ArrayList<>(members.keySet());
-    Collections.sort(keys);
-    ArrayList<T> result = new ArrayList<>(members.size());
-    for (Collection<T> values : members.values()) {
-      result.addAll(values);
-    }
-    return result;
-  }
-
 }
diff --git a/src/main/java/org/extendj/util/Sorting.java b/src/main/java/org/extendj/util/Sorting.java
new file mode 100644
index 0000000000000000000000000000000000000000..db10ddbfa7f5876b681b9d8a3a324d76f3d44a97
--- /dev/null
+++ b/src/main/java/org/extendj/util/Sorting.java
@@ -0,0 +1,103 @@
+/* Copyright (c) 2018, Jesper Öqvist <jesper.oqvist@cs.lth.se>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.extendj.util;
+
+import org.extendj.ast.TypeDecl;
+import se.llbit.json.JsonObject;
+import se.llbit.json.JsonValue;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Helper functions for sorting collections.
+ */
+public class Sorting {
+  public interface KeyFun<T, R> {
+    R apply(T t);
+  }
+
+  // These can be converted to Java 8 lambdas:
+
+  public static final KeyFun<JsonValue, String> objectNameFun = new KeyFun<JsonValue, String>() {
+    @Override public String apply(JsonValue value) {
+      return value.object().get("name").asString("");
+    }
+  };
+
+  public static final KeyFun<JsonObject, String> objectNameKeyFun = new KeyFun<JsonObject, String>() {
+    @Override public String apply(JsonObject value) {
+      return value.object().get("name").asString("");
+    }
+  };
+
+  public static final KeyFun<TypeDecl, String> typeNameFun = new KeyFun<TypeDecl, String>() {
+    @Override public String apply(TypeDecl type) {
+      return type.name();
+    }
+  };
+  public static final KeyFun<JsonValue, String> jsonStringFun = new KeyFun<JsonValue, String>() {
+    @Override public String apply(JsonValue value) {
+      return value.asString("");
+    }
+  };
+
+  /**
+   * Returns a sorted JSON array based on the argument elements and a key function.
+   *
+   * <p>The result preserves duplicate elements and elements with identical keys.</p>
+   */
+  public static <T, K extends Comparable<? super K>> List<T> sortBy(
+      Iterable<T> iterable,
+      KeyFun<T, K> keyFun) {
+    Map<K, Collection<T>> members = new TreeMap<>();
+    for (T value : iterable) {
+      K sortKey = keyFun.apply(value);
+      Collection<T> items = members.get(sortKey);
+      if (items == null) {
+        items = new ArrayList<>();
+        members.put(sortKey, items);
+      }
+      items.add(value);
+    }
+    List<K> keys = new ArrayList<>(members.keySet());
+    Collections.sort(keys);
+    ArrayList<T> result = new ArrayList<>(members.size());
+    for (Collection<T> values : members.values()) {
+      result.addAll(values);
+    }
+    return result;
+  }
+
+}