Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Draft] @JsonUnboxed annotation #86

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

mitasov-ra
Copy link
Contributor

@mitasov-ra mitasov-ra commented Jul 26, 2024

Added new annotation @JsonUnboxed to indicate that class should be (de)serialized as it's underlying field.

It can only be used on value classes. This draft atm contains only Java implementation to raise a discussion. Kotlin support is to be added to this PR.

Reasoning

The original intention was to add implicit conversion of Kotlin's value class to/from JSON as it's field value. But after taking a closer look at value class I realised that it has all the properties of regular class, and implicit conversion is not an option.

Then I looked it up and found similar approach in Rust's Serde, where this conversion is made explicit by adding transparent to serde attribute.

Naming

I've picked Unboxed as this feature reminded me Java's Autoboxing and Unboxing.

Unsolved problems

By placing @JsonReader on single parameter constructor and by marking all the fields except one with @JsonSkip it is possible to use @JsonUnboxed with regular classes. Is it good? Is it bad? I've added tests for this case.

It is possible to restrict usage of this annotation only to single field records, for example. Discussion is needed.

Similar technologies in other frameworks/languages

Jackson

@JsonValue annotation can be placed on a class' field to serialize an entire object using this single field. @JsonValue can be placed on getter methods too.

BUT there's a problem with deserialization. The following code

class ValueTest {
    private final String myValue;

    ValueTest(String myValue) { this.myValue = myValue; }
    ValueTest(String a,String b) { this(a + b); }

    // @JsonValue <-- THIS IS COMMENTED OUT
    public String getMyValue() { return myValue; }

    public String toString() { return "ValueTest(" + myValue + ")"; }
}

// boilerplate omitted
public static void main(String[] args) throws JsonProcessingException {
    var valueTest = new ValueTest("123");
    System.out.println(new ObjectMapper().writeValueAsString(valueTest));
    var test2 = new ObjectMapper().readValue("\"hi!\"", new TypeReference<ValueTest>() {});
    System.out.println(test2);
}

Will output:

{"myValue":"123"}
ValueTest(hi!)

This is because from-value deserialization is used by default, and from-object is explicitly enabled by adding @JsonCreator on constructor and @JsonProperty on String myValue parameter. This is very confusing and inconsistent.

NOTE: We can create @JsonValue annotation in Kora but make corresponding behavior consistent between reading and writing JSON.

Gson

TODO

Rust serde

https://serde.rs/container-attrs.html#transparent

#[serde(transparent)]
Serialize and deserialize a newtype struct or a braced struct with one field exactly the same as if its one field were serialized and deserialized by itself.

#[serde(transparent)]
struct W {
    a: i32
}

@GoodforGod GoodforGod marked this pull request as draft August 27, 2024 10:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant