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

Json error is not deserialized correctly #691

Open
melix opened this issue Nov 22, 2023 · 5 comments
Open

Json error is not deserialized correctly #691

melix opened this issue Nov 22, 2023 · 5 comments

Comments

@melix
Copy link
Contributor

melix commented Nov 22, 2023

Expected Behavior

It should be possible to deserialize to a JsonError class.

Actual Behaviour

Deserialization produces an incomplete result.

Steps To Reproduce

The following test case fails:

package io.micronaut.serde.jackson

import io.micronaut.http.hateoas.JsonError
import io.micronaut.http.hateoas.Resource
import io.micronaut.serde.ObjectMapper
import spock.lang.Specification

class JsonErrorSpec extends Specification {
    void "JsonError should be deserializable from a string"() {
        setup:
        ObjectMapper objectMapper = ObjectMapper.getDefault()

        when:
        JsonError deserializationResult = objectMapper.readValue('{"_links":{"self":[{"href":"/resolve","templated":false}]},"_embedded":{"errors":[{"message":"Internal Server Error: Something bad happened"}]},"message":"Internal Server Error"}', JsonError)

        then:
        deserializationResult.message == 'Internal Server Error'
        deserializationResult.embedded.getFirst('errors').present

    }

    void "can deserialize a Json error as a generic resource"() {
        setup:
        ObjectMapper objectMapper = ObjectMapper.getDefault()

        when:
        Resource deserializationResult = objectMapper.readValue('{"_links":{"self":[{"href":"/resolve","templated":false}]},"_embedded":{"errors":[{"message":"Internal Server Error: Something bad happened"}]},"message":"Internal Server Error"}', Resource)

        then:
        deserializationResult != null

    }
}

Environment Information

No response

Example Application

No response

Version

4.2.0

@andrebrov
Copy link

We have the same issue. It would be great to get any update

@altro3
Copy link
Contributor

altro3 commented Feb 4, 2024

It's funny, but it never worked. I found a test written 2.5 years ago:

изображение

Here is a simplified class structure to reproduce the problem.

package io.micronaut.serde.support;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.annotation.ReflectiveAccess;
import io.micronaut.core.convert.value.ConvertibleValues;
import io.micronaut.core.util.StringUtils;
import io.micronaut.core.value.OptionalMultiValues;
import io.micronaut.http.hateoas.Link;
import io.micronaut.http.hateoas.Resource;
import io.micronaut.serde.annotation.Serdeable;

import com.fasterxml.jackson.annotation.JsonProperty;

import static io.micronaut.http.hateoas.Resource.EMBEDDED;
import static io.micronaut.http.hateoas.Resource.LINKS;

@Serdeable
public class TestJsonError<Impl extends TestJsonError> {

    private final Map<CharSequence, List<Link>> linkMap = new LinkedHashMap<>(1);
    private final Map<CharSequence, List<Resource>> embeddedMap = new LinkedHashMap<>(1);

    @JsonProperty(LINKS)
    public OptionalMultiValues<Link> getLinks() {
        return OptionalMultiValues.of(linkMap);
    }

    @JsonProperty(EMBEDDED)
    public OptionalMultiValues<Resource> getEmbedded() {
        return OptionalMultiValues.of(embeddedMap);
    }

    @SuppressWarnings("unchecked")
    @Internal
    @ReflectiveAccess
    @JsonProperty(LINKS)
    public final void setLinks(Map<String, Object> links) {
        for (Map.Entry<String, Object> entry : links.entrySet()) {
            String name = entry.getKey();
            Object value = entry.getValue();
            if (value instanceof Map) {
                Map<String, Object> linkMap = (Map<String, Object>) value;
                link(name, linkMap);
            }
        }
    }

    @Internal
    @ReflectiveAccess
    @JsonProperty(EMBEDDED)
    public final void setEmbedded(Map<String, List<Resource>> embedded) {
        embeddedMap.putAll(embedded);
    }

    private void link(String name, Map<String, Object> linkMap) {
        ConvertibleValues<Object> values = ConvertibleValues.of(linkMap);
        Optional<String> uri = values.get(Link.HREF, String.class);
        uri.ifPresent(uri1 -> {
            Link.Builder link = Link.build(uri1);
            values.get("templated", Boolean.class)
                .ifPresent(link::templated);
            values.get("hreflang", String.class)
                .ifPresent(link::hreflang);
            values.get("title", String.class)
                .ifPresent(link::title);
            values.get("profile", String.class)
                .ifPresent(link::profile);
            values.get("deprecation", String.class)
                .ifPresent(link::deprecation);
            link(name, link.build());
        });
    }

    public Impl link(@Nullable CharSequence ref, @Nullable Link link) {
        if (StringUtils.isNotEmpty(ref) && link != null) {
            List<Link> links = this.linkMap.computeIfAbsent(ref, charSequence -> new ArrayList<>());
            links.add(link);
        }
        return (Impl) this;
    }
}

The problem is clearly that serde cannot interpret the methods correctly :

    @JsonProperty(LINKS)
    public final void setLinks(Map<String, Object> links) {

and

    @JsonProperty(EMBEDDED)
    public final void setEmbedded(Map<String, List<Resource>> embedded) {

@dstepanov It turns out that the analyzer for JsonProperty is not working correctly now.

@dstepanov
Copy link
Contributor

At this moment, we ignore setters with a different type than the getter. This needs to be adjusted in Core, possibly distinguishing read and write types.

@altro3
Copy link
Contributor

altro3 commented Feb 5, 2024

Hm.... is the problem in the core? Why can't we take the right method in the heart and work with it? I mean, get a method with a JsonProperty annotation, understand that this is a setter for a certain field, and perform the necessary transformations.

I'm thinking about how I would do it in openapi, maybe serde has a tighter binding to the core and it's not that easy to do it.

@dstepanov
Copy link
Contributor

The introspection needs to be improved to allow read/write types; right now, it does PropertyElementQuery.of(ce).ignoreSettersWithDifferingType(true) if you use ce.getBeanProperties() the default value is to include different setters, so it might work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Status: No status
Development

No branches or pull requests

4 participants