diff --git a/src/main/java/org/springframework/hateoas/Link.java b/src/main/java/org/springframework/hateoas/Link.java index 1831633fb..6de7e672f 100755 --- a/src/main/java/org/springframework/hateoas/Link.java +++ b/src/main/java/org/springframework/hateoas/Link.java @@ -34,6 +34,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; /** * Value object for links. @@ -43,6 +44,7 @@ * @author Jens Schauder */ @JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({"rel", "href", "hreflang", "media", "title", "type", "deprecation", "profile", "name", "template", "affordances"}) @JsonIgnoreProperties(value = { "templated", "template" }, ignoreUnknown = true) public class Link implements Serializable { diff --git a/src/main/java/org/springframework/hateoas/PagedModel.java b/src/main/java/org/springframework/hateoas/PagedModel.java index 354139a04..f40e8eac6 100644 --- a/src/main/java/org/springframework/hateoas/PagedModel.java +++ b/src/main/java/org/springframework/hateoas/PagedModel.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; /** * DTO to implement binding response representations of pageable collections. @@ -388,6 +389,7 @@ public int hashCode() { * * @author Oliver Gierke */ + @JsonPropertyOrder({"size", "totalElements", "totalPages", "number"}) public static class PageMetadata { @JsonProperty private long size; diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsProperty.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsProperty.java index f52a672b8..865397266 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsProperty.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsProperty.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; /** * Describe a parameter for the associated state transition in a HAL-FORMS document. A {@link HalFormsTemplate} may @@ -35,6 +36,7 @@ * @see https://mamund.site44.com/misc/hal-forms/ */ @JsonInclude(Include.NON_DEFAULT) +@JsonPropertyOrder({"name", "prompt", "regex", "placeholder", "value", "templated", "multi", "readOnly", "required", "min", "max", "minLength", "maxLength", "type", "options"}) final class HalFormsProperty implements Named { private final String name, prompt, regex, placeholder; diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilder.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilder.java index d08eb5c4c..4da0ce106 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilder.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilder.java @@ -15,7 +15,7 @@ */ package org.springframework.hateoas.mediatype.hal.forms; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; @@ -53,7 +53,7 @@ public HalFormsTemplateBuilder(HalFormsConfiguration configuration, MessageResol */ public Map findTemplates(RepresentationModel resource) { - Map templates = new HashMap<>(); + Map templates = new LinkedHashMap<>(); Link selfLink = resource.getLink(IanaLinkRelations.SELF).orElse(null); resource.getLinks().stream() // diff --git a/src/main/java/org/springframework/hateoas/mediatype/problem/Problem.java b/src/main/java/org/springframework/hateoas/mediatype/problem/Problem.java index c379416a6..e9fc97472 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/problem/Problem.java +++ b/src/main/java/org/springframework/hateoas/mediatype/problem/Problem.java @@ -33,6 +33,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonUnwrapped; /** @@ -42,6 +43,7 @@ * @author Greg Turnquist * @author Oliver Drotbohm */ +@JsonPropertyOrder({"type", "title", "detail", "instance"}) @JsonInclude(Include.NON_NULL) public class Problem { diff --git a/src/test/java/org/springframework/hateoas/EntityModelIntegrationTest.java b/src/test/java/org/springframework/hateoas/EntityModelIntegrationTest.java index 8c094e7e8..6f16c02e8 100755 --- a/src/test/java/org/springframework/hateoas/EntityModelIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/EntityModelIntegrationTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -87,6 +88,7 @@ public PersonModel(Person person) { protected PersonModel() {} } + @JsonPropertyOrder({"firstname", "lastname"}) @JsonAutoDetect(fieldVisibility = Visibility.ANY) static class Person { diff --git a/src/test/java/org/springframework/hateoas/IanaLinkRelationUnitTest.java b/src/test/java/org/springframework/hateoas/IanaLinkRelationUnitTest.java index 251ed6b1a..8213d980d 100644 --- a/src/test/java/org/springframework/hateoas/IanaLinkRelationUnitTest.java +++ b/src/test/java/org/springframework/hateoas/IanaLinkRelationUnitTest.java @@ -16,6 +16,7 @@ package org.springframework.hateoas; import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import lombok.Value; @@ -107,7 +108,7 @@ void testAllIanaLinkRelationsHaveStringConstant() { .map(String.class::cast) // .collect(Collectors.toSet()); - assertThat(linkRelations).containsExactlyElementsOf(stringConstants); + assertEquals(linkRelations, stringConstants); } /** diff --git a/src/test/java/org/springframework/hateoas/Jackson2PagedResourcesIntegrationTest.java b/src/test/java/org/springframework/hateoas/Jackson2PagedResourcesIntegrationTest.java index 1541d25a1..dc2813e8f 100755 --- a/src/test/java/org/springframework/hateoas/Jackson2PagedResourcesIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/Jackson2PagedResourcesIntegrationTest.java @@ -24,6 +24,8 @@ import java.lang.reflect.Type; import java.util.Collections; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + import org.apache.commons.io.output.WriterOutputStream; import org.junit.jupiter.api.Test; import org.springframework.hateoas.PagedModel.PageMetadata; @@ -86,6 +88,7 @@ interface Sample { CollectionModel someMethod(); } + @JsonPropertyOrder({"firstname", "lastname"}) static class User { public String firstname, lastname; } diff --git a/src/test/java/org/springframework/hateoas/Jackson2ResourceIntegrationTest.java b/src/test/java/org/springframework/hateoas/Jackson2ResourceIntegrationTest.java index df3602617..73ba1afa1 100755 --- a/src/test/java/org/springframework/hateoas/Jackson2ResourceIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/Jackson2ResourceIntegrationTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; /** * Integration tests for {@link EntityModel}. @@ -54,6 +55,7 @@ public PersonResource() { } } + @JsonPropertyOrder({"firstname", "lastname"}) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) static class Person { diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/HalModelBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/HalModelBuilderUnitTest.java index 4df712820..a0983de49 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/HalModelBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/HalModelBuilderUnitTest.java @@ -49,6 +49,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -387,6 +388,7 @@ private void assertEmptyEmbed(RepresentationModel model, String name) throws @Value @AllArgsConstructor + @JsonPropertyOrder({"name", "born", "died"}) static class Author { private String name; @@ -404,6 +406,7 @@ static class Staff { @Value @AllArgsConstructor + @JsonPropertyOrder({"name", "price"}) static class Product { private String name; diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java index 4e71ee783..ab40e3bde 100755 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java @@ -58,6 +58,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.PropertyNamingStrategy; @@ -721,6 +722,9 @@ private ObjectMapper getCuriedObjectMapper(CurieProvider provider, @Nullable Mes mapper.registerModule(new Jackson2HalModule()); mapper.setHandlerInstantiator( new HalHandlerInstantiator(new AnnotationLinkRelationProvider(), provider, MessageResolver.of(messageSource))); + + // the current alternative SerializationFeature.ORDERED_MAP_ENTRIES_BY_KEYS does not sort nested keys + mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); return mapper; } diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/SimplePojo.java b/src/test/java/org/springframework/hateoas/mediatype/hal/SimplePojo.java index 9eebf931a..fe390d496 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/SimplePojo.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/SimplePojo.java @@ -6,7 +6,9 @@ import lombok.NoArgsConstructor; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +@JsonPropertyOrder({"text", "number"}) @Data @Getter(onMethod = @__(@JsonProperty)) @NoArgsConstructor diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java index 2983ab0f5..51bb43149 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java @@ -76,6 +76,7 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.jayway.jsonpath.DocumentContext; @@ -668,6 +669,8 @@ private ContextualMapper getCuriedObjectMapper(CurieProvider provider, @Nullable return MappingTestUtils.createMapper(Jackson2HalFormsIntegrationTest.class, configurer.andThen(it -> { it.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(this.provider, provider, resolver, new HalConfiguration(), factory)); + // the current alternative SerializationFeature.ORDERED_MAP_ENTRIES_BY_KEYS does not sort nested keys + it.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); })); } diff --git a/src/test/java/org/springframework/hateoas/mediatype/problem/JacksonSerializationTest.java b/src/test/java/org/springframework/hateoas/mediatype/problem/JacksonSerializationTest.java index bf2c17b8e..6bdca6ad7 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/problem/JacksonSerializationTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/problem/JacksonSerializationTest.java @@ -42,6 +42,7 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; @@ -332,6 +333,7 @@ static class Sample { */ @Value @Getter(onMethod = @__(@JsonProperty)) + @JsonPropertyOrder({"balance", "accounts"}) @RequiredArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(staticName = "empty", force = true) @With diff --git a/src/test/java/org/springframework/hateoas/server/core/TypeReferencesIntegrationTest.java b/src/test/java/org/springframework/hateoas/server/core/TypeReferencesIntegrationTest.java index 2ce62215d..b6bae8421 100755 --- a/src/test/java/org/springframework/hateoas/server/core/TypeReferencesIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/server/core/TypeReferencesIntegrationTest.java @@ -24,6 +24,8 @@ import java.util.Collection; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -218,6 +220,7 @@ private static void assertExpectedUser(User user) { } @Data + @JsonPropertyOrder({"firstname", "lastname"}) static class User { public String firstname, lastname; } diff --git a/src/test/java/org/springframework/hateoas/support/Employee.java b/src/test/java/org/springframework/hateoas/support/Employee.java index 5c9d9a9a6..f6a9c6803 100644 --- a/src/test/java/org/springframework/hateoas/support/Employee.java +++ b/src/test/java/org/springframework/hateoas/support/Employee.java @@ -22,6 +22,7 @@ import javax.validation.constraints.NotNull; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; /** * @author Greg Turnquist @@ -30,6 +31,7 @@ @With @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) +@JsonPropertyOrder({"name", "role"}) public class Employee { private @NotNull String name;