FasterXML / jackson-databind

General data-binding package for Jackson (2.x): works on streaming API (core) implementation(s)
Apache License 2.0
3.52k stars 1.38k forks source link

Field order mismatch after serialization and deserialization #4751

Open arvgord opened 1 week ago

arvgord commented 1 week ago

Search before asking

Describe the bug

I am encountering a problem where the field order is not preserved after serializing and deserializing objects in Jackson.

I need access to only one specific field (transactionId), while I want to pass the remaining fields as they are, preserving the order in which they appear. I expected the transactionId field order might change, but I did not expect the order of the other fields (a, b, c) to change, especially in the second and third tests (SecondObject and ThirdObject). The data inside the c array is preserved in the expected order, but the surrounding fields are re-ordered.

Version Information

2.17.2

Reproduction

Tested Objects:

@JsonAutoDetect(
    fieldVisibility = JsonAutoDetect.Visibility.ANY,
    getterVisibility = JsonAutoDetect.Visibility.NONE
)
class FirstObject {
    @field:JsonAnySetter
    @field:JsonAnyGetter
    val data: Map<String, Any?> = LinkedHashMap()

    val transactionId: String
        get() = data["transactionId"] as String
}

@JsonAutoDetect(
    fieldVisibility = JsonAutoDetect.Visibility.ANY,
    getterVisibility = JsonAutoDetect.Visibility.NONE
)
class SecondObject(
    val transactionId: String
) {
    @field:JsonAnySetter
    @field:JsonAnyGetter
    val data: Map<String, Any?> = LinkedHashMap()
}

@JsonAutoDetect(
    fieldVisibility = JsonAutoDetect.Visibility.ANY,
    getterVisibility = JsonAutoDetect.Visibility.NONE
)
class ThirdObject(
    val transactionId: String,
    @field:JsonAnySetter
    @field:JsonAnyGetter
    val data: Map<String, Any?> = LinkedHashMap()
)

Input JSON for tests:

{
  "b": 2,
  "a": 1,
  "transactionId": "test",
  "c": [
    {
      "id": "3",
      "value": "c"
    },
    {
      "id": "1",
      "value": "a"
    },
    {
      "id": "2",
      "value": "b"
    }
  ]
}

Test Case:

Here’s a test case to reproduce the issue. The following Kotlin test serializes and deserializes three different classes (FirstObject, SecondObject, and ThirdObject), comparing the serialized JSON with the original input JSON. JacksonSortingTest.kt.

First Test Result (Passes):

The first test for FirstObject passes as expected, with the field order being preserved.

Failing Test Results:

Expected:

{
  "b": 2,
  "a": 1,
  "transactionId": "test",
  "c": [
    {
      "id": "3",
      "value": "c"
    },
    {
      "id": "1",
      "value": "a"
    },
    {
      "id": "2",
      "value": "b"
    }
  ]
}

For SecondObject was:

{
  "transactionId": "test",
  "a": 1,
  "b": 2,
  "c": [
    {
      "id": "3",
      "value": "c"
    },
    {
      "id": "1",
      "value": "a"
    },
    {
      "id": "2",
      "value": "b"
    }
  ]
}

For ThirdObject was:

{
  "transactionId": "test",
  "c": [
    {
      "id": "3",
      "value": "c"
    },
    {
      "id": "1",
      "value": "a"
    },
    {
      "id": "2",
      "value": "b"
    }
  ],
  "a": 1,
  "b": 2
}

Repository for Reproduction: You can find a repository with the full reproduction of the issue at jackson-databind-sorting-issue.

Expected behavior

For SecondObject and ThirdObject, after serializing the object back to JSON, I expect the transactionId field to appear at the top, followed by the fields b, a, and c, in the exact order they were present when the JSON was deserialized into SecondObject and ThirdObject.

Additional context

No response

arvgord commented 1 week ago

I tried to update from 2.17.2 to 2.18.0 but found that serialization was broken. Issue #4752

cowtowncoder commented 1 week ago

Same as with other issue:

cowtowncoder commented 1 week ago

Could you please include test input JSON inline in description, and not just in included (Kotlin) test? It is hard to follow description wrt "a", "b" and "c" when Objects do not have such fields -- I assume they are for "any properties" from JSON.

arvgord commented 1 week ago

Could you please include test input JSON inline in description, and not just in included (Kotlin) test? It is hard to follow description wrt "a", "b" and "c" when Objects do not have such fields -- I assume they are for "any properties" from JSON.

I have now included the test input JSON directly in the issue description for better clarity.

To clarify, I expect that all data from the JSON, which is not explicitly defined as transactionId field, will be set in the data map using @JsonAnySetter.

arvgord commented 1 week ago

I created tests for Java: https://github.com/arvgord/jackson-databind-sorting-issue-java. For Jackson version 2.17.2, everything works fine. The field order is preserved as I expected:

{
    "transactionId": "test",
    "b": 2,
    "a": 1,
    "c": [
        {
            "id": "3",
            "value": "c"
        },
        {
            "id": "1",
            "value": "a"
        },
        {
            "id": "2",
            "value": "b"
        }
    ]
}

It seems that the issue is related to jackson-module-kotlin #842. Therefore, this issue can be closed.

arvgord commented 13 hours ago

I can reproduce this issue with Java, so I reopened it. Here’s the test to reproduce.

pjfanning commented 12 hours ago

Just because the code used to work a certain way and now doesn't work that way - that doesn't mean the old way was intended or that the change is a regression or a bug.

With Serialization, the one change that I can see in the release notes is #4580.

I don't even know how Jackson is supposed to preserve the order. When you deserialize to Java object instance and then serialize the Java object instance - there is currently nothing stored on the Java object instance to say what the order of the original JSON input was.

arvgord commented 11 hours ago

I'm encountering different field orders when serializing objects, which is confusing. It would be helpful if Jackson supported a unified field order by default, perhaps based on the order of fields in the constructor. Consistency in field order would improve predictability and make it easier to manage serialization across various use cases. Is there any chance of supporting a standardized order as the default in future versions?

pjfanning commented 11 hours ago

Read https://github.com/FasterXML/jackson-databind/issues/4580

That is predicatable, isn't it?