Grails
  1. Grails
  2. GRAILS-8849

JSON Serialization of Sets results in loss of order

    Details

    • Type: Bug Bug
    • Status: Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: None
    • Fix Version/s: None
    • Component/s: Commons
    • Labels:
      None

      Description

      When converting to Json using the JSON object, a Set is converted using a HashSet, rather than a LinkedHashSet
      Looks like DomainClassMarshaller.marshalObject
      around line 127 in grails-plugin-converters-2.0.1

      else if (referenceObject instanceof Set)

      { referenceObject = new HashSet((Set) referenceObject); }

      See this forum item for more detail:
      http://grails.1312388.n4.nabble.com/Ordering-problems-with-Sets-in-JSON-tt4411982.html

        Activity

        Hide
        Bobby Warner added a comment -

        Sets don't have order, use a List (http://grails.org/doc/latest/guide/GORM.html#sets,ListsAndMaps). This is not a bug, recommend closing. Thanks, Bobby

        Show
        Bobby Warner added a comment - Sets don't have order, use a List ( http://grails.org/doc/latest/guide/GORM.html#sets,ListsAndMaps ). This is not a bug, recommend closing. Thanks, Bobby
        Hide
        Adam Krieg added a comment -

        Technically there are no Sets in JSON, only Maps and Lists. The principle of least surprise would be to treat Sets as lists and respect the order of the Set. What is the harm in preserving the input order? What if the input set WAS a LinkedHashSet? The order that the user specified would be lost.

        Show
        Adam Krieg added a comment - Technically there are no Sets in JSON, only Maps and Lists. The principle of least surprise would be to treat Sets as lists and respect the order of the Set. What is the harm in preserving the input order? What if the input set WAS a LinkedHashSet? The order that the user specified would be lost.
        Hide
        Bobby Warner added a comment -

        I'm either misunderstanding you or simply just not getting the problem... If you have your collection mapped as a List and render it as JSON, is the order not preserved? Why are you trying to use LinkedHashSet as opposed to just a List? What is the actual issue that using a List doesn't solve? Please let me know.

        Thanks,
        Bobby

        Show
        Bobby Warner added a comment - I'm either misunderstanding you or simply just not getting the problem... If you have your collection mapped as a List and render it as JSON, is the order not preserved? Why are you trying to use LinkedHashSet as opposed to just a List? What is the actual issue that using a List doesn't solve? Please let me know. Thanks, Bobby
        Hide
        Adam Krieg added a comment - - edited

        Sorry if I'm not being clear.

        From my example on the mailing list, you can see my two classes and their relation:

        class Parent

        { static hasMany = [children: Child] }

        class Child {

        static belongsTo = [parent: Parent]

        static mapping =

        { sort 'id' }

        }

        I did not map my collection as a List as I was unaware I needed to in order to get insertion sort ordering for my collection. From the rest of my application's perspective, the order is correct.
        e.g.:
        parent.children

        returns a set of child Objects that are sorted by the order they were created (or something else, ID?) I'm not sure why the order is correct as I'm not implementing comparable or overriding hashCode anywhere in my domain objects.

        I can declare my collection as a List in GORM, (although I can't really have any duplicates in practice) but this seems to be a weird artifact of how the JSON DomainClassMarshaller works. Its behavior is not consistent with the CollectionMarshaller, which just serializes the collection in order.

        For example, calling:
        parent.children as JSON
        gives me the correct order.

        Why does the DomainClassMarshaller go to great lengths to determine whether a set is sorted or not, when it could just preserve input order using a List and be done with it?

        You could replace all of this code:

        if (referenceObject instanceof SortedMap)

        { referenceObject = new TreeMap((SortedMap) referenceObject); }

        else if (referenceObject instanceof SortedSet)

        { referenceObject = new TreeSet((SortedSet) referenceObject); }

        else if (referenceObject instanceof Set)

        { referenceObject = new HashSet((Set) referenceObject); }

        else if (referenceObject instanceof Map)

        { referenceObject = new HashMap((Map) referenceObject); }

        else if (referenceObject instanceof Collection)

        { referenceObject = new ArrayList((Collection) referenceObject); }


        with

        if (referenceObject instanceof Map) { referenceObject = new LinkedHashMap((Map)referenceObject); } else { referenceObject = new ArrayList((Collection) referenceObject); }
        Show
        Adam Krieg added a comment - - edited Sorry if I'm not being clear. From my example on the mailing list, you can see my two classes and their relation: class Parent { static hasMany = [children: Child] } class Child { static belongsTo = [parent: Parent] static mapping = { sort 'id' } } I did not map my collection as a List as I was unaware I needed to in order to get insertion sort ordering for my collection. From the rest of my application's perspective, the order is correct. e.g.: parent.children returns a set of child Objects that are sorted by the order they were created (or something else, ID?) I'm not sure why the order is correct as I'm not implementing comparable or overriding hashCode anywhere in my domain objects. I can declare my collection as a List in GORM, (although I can't really have any duplicates in practice) but this seems to be a weird artifact of how the JSON DomainClassMarshaller works. Its behavior is not consistent with the CollectionMarshaller, which just serializes the collection in order. For example, calling: parent.children as JSON gives me the correct order. Why does the DomainClassMarshaller go to great lengths to determine whether a set is sorted or not, when it could just preserve input order using a List and be done with it? You could replace all of this code: if (referenceObject instanceof SortedMap) { referenceObject = new TreeMap((SortedMap) referenceObject); } else if (referenceObject instanceof SortedSet) { referenceObject = new TreeSet((SortedSet) referenceObject); } else if (referenceObject instanceof Set) { referenceObject = new HashSet((Set) referenceObject); } else if (referenceObject instanceof Map) { referenceObject = new HashMap((Map) referenceObject); } else if (referenceObject instanceof Collection) { referenceObject = new ArrayList((Collection) referenceObject); } with if (referenceObject instanceof Map) { referenceObject = new LinkedHashMap((Map)referenceObject); } else { referenceObject = new ArrayList((Collection) referenceObject); }
        Hide
        Adam Krieg added a comment -

        I left out a key detail from the example. The parent has a static mapping =

        { children sort: 'time', order: 'asc'}

        which explains why the children are sorted in the Parent Domain class.

        If I were to declare children as a List, as you have pointed me to in the documentation:
        http://grails.org/doc/latest/guide/GORM.html#sets,ListsAndMaps , I would need to declare an idx column in my child table to perform the sorting for the list. In my use case, I'm only reading data from the db, never adding new children and saving them, so perhaps that's why I'm not getting bitten by sorting problems in the domain. If I can get away with not adding this extra column to my child table, I would prefer to do so.

        Show
        Adam Krieg added a comment - I left out a key detail from the example. The parent has a static mapping = { children sort: 'time', order: 'asc'} which explains why the children are sorted in the Parent Domain class. If I were to declare children as a List, as you have pointed me to in the documentation: http://grails.org/doc/latest/guide/GORM.html#sets,ListsAndMaps , I would need to declare an idx column in my child table to perform the sorting for the list. In my use case, I'm only reading data from the db, never adding new children and saving them, so perhaps that's why I'm not getting bitten by sorting problems in the domain. If I can get away with not adding this extra column to my child table, I would prefer to do so.
        Hide
        Peter Ledbrook added a comment -

        This issue ignores SortedSet, which should become a JSON array with the same order as the sorted set. I think using LinkedHashSet for the defensive copy is appropriate. It would maintain the order of both SortedSet and LinkedHashSet.

        Show
        Peter Ledbrook added a comment - This issue ignores SortedSet , which should become a JSON array with the same order as the sorted set. I think using LinkedHashSet for the defensive copy is appropriate. It would maintain the order of both SortedSet and LinkedHashSet .

          People

          • Assignee:
            Unassigned
            Reporter:
            Adam Krieg
          • Votes:
            1 Vote for this issue
            Watchers:
            4 Start watching this issue

            Dates

            • Created:
              Updated:

              Development