Skip to content

Long field value breaks parsing #5098

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

Closed
1 task done
KyryloSemenkoDiversium opened this issue Apr 17, 2025 · 15 comments
Closed
1 task done

Long field value breaks parsing #5098

KyryloSemenkoDiversium opened this issue Apr 17, 2025 · 15 comments

Comments

@KyryloSemenkoDiversium
Copy link

Search before asking

  • I searched in the issues and found nothing similar.

Describe the bug

I have the following structure to deserialize
{ "event_type": "switch_state", "event": { "model": { "units": [], "ports": [], "limits": { "atx": { "click_delays": { "power": { "default": 0.5, "min": 0, "max": 10 }, "power_long": { "default": 5.5, "min": 0, "max": 10 }, "reset": { "default": 0.5, "min": 0, "max": 10 } } } } }, "summary": { "active_port": -1, "synced": true }, "edids": { "all": { "default": { "name": "Default", "data": "00FFFFFFFFFFFF0031D8737701010101231A010380351E780E0565A756529C270F50543FED00B300A9C0950090408180814081C0714F023A801871382D40582C45000F282100001E000000FF0043414645424142452020202020000000FD00324B0F5211000A202020202020000000FC0050694B564D20563420506C7573012D020320714B90041F13223E213D203C0167030C001000802D23097F0783010000023A801871382D40582C45000F282100001E011D007251D01E206E2855000F282100001E023A80D072382D40102C45800F282100001E283C80A070B023403020360006442100001A000000000000000000000000000000000000000000000030", "parsed": { "mfc_id": "LNX", "product_id": 30579, "serial": 16843009, "monitor_name": "PiKVM V4 Plus", "monitor_serial": "CAFEBABE", "audio": true } } }, "used": [] }, "colors": { "beacon": { "blink_ms": 250, "brightness": 255, "red": 228, "blue": 156, "green": 44 }, "flashing": { "blink_ms": 0, "brightness": 128, "red": 0, "blue": 255, "green": 170 }, "bootloader": { "blink_ms": 0, "brightness": 128, "red": 255, "blue": 0, "green": 170 }, "inactive": { "blink_ms": 0, "brightness": 64, "red": 255, "blue": 0, "green": 0 }, "active": { "blink_ms": 0, "brightness": 128, "red": 0, "blue": 0, "green": 255 } }, "video": { "links": [] }, "usb": { "links": [] }, "beacons": { "uplinks": [], "downlinks": [], "ports": [] }, "atx": { "busy": [], "leds": { "power": [], "hdd": [] } } } }

The "mfc_id" field was incorrectly assigned to the "default" object and not to the "parsed" object.

All fields from object "parsed" incorrectly considered as fields from object "default".

Thank you very much for the great de/serialization tool. It's the best in the world.

Version Information

2.17.2

Reproduction

<-- Any of the following

  1. Brief code sample/snippet: include here in preformatted/code section
  2. Longer example stored somewhere else (diff repo, snippet), add a link
  3. Textual explanation: include here
    -->
// Your code here

Expected behavior

No response

Additional context

No response

@KyryloSemenkoDiversium KyryloSemenkoDiversium added the to-evaluate Issue that has been received but not yet evaluated label Apr 17, 2025
@pjfanning
Copy link
Member

pjfanning commented Apr 17, 2025

Please try Jackson 2.18.3. This sounds like an issue that we fixed in 2.18.3.

FasterXML/jackson-core#1397

@pjfanning
Copy link
Member

If this also fails in v2.18.3 - could you also provide us with a more reproducible case? This includes providing the class(es) that you are deserializing to and indications about how you are setting up your ObjectMapper (because the mapper is very configurable).

@KyryloSemenkoDiversium
Copy link
Author

Yes, of course

    @Bean(name = JSON_OBJECT_MAPPER)
    public ObjectMapper jsonObjectMapper() {
        return getJsonObjectMapper();
    }

    public static ObjectMapper getJsonObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.findAndRegisterModules();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
        objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
        return objectMapper;
    }
    private final ObjectMapper jsonObjectMapper;

    public PikvmWebSocketClient(ApplicationContext applicationContext) {
        this.jsonObjectMapper = applicationContext.getBean(ObjectMapperConfig.JSON_OBJECT_MAPPER, ObjectMapper.class);
    }
    private void parse(WebSocketMessage msg) {
        if (true) {
            try {
                EventWrapper eventWrapper = jsonObjectMapper.readValue(msg.getPayloadAsText(), EventWrapper.class);
                log.debug("Received message: {}", eventWrapper.getEventType());
            } catch (Throwable t) {
                log.error("Failed to parse message: {}", msg.getPayloadAsText(), t);
            }
        }
    }

@KyryloSemenkoDiversium
Copy link
Author

And data objects

api.zip

@KyryloSemenkoDiversium
Copy link
Author

Please give me some more time to test on v2.18.3.

I'll let you know.

@KyryloSemenkoDiversium
Copy link
Author

Same behavior in version 2.18.3.

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "mfc_id" (class com.diversium.duckserver.pikvm.api.EdidInfo), not marked as ignorable (3 known properties: "parsed", "data", "name"]) at [Source: REDACTED (StreamReadFeature.INCLUDE_SOURCE_IN_LOCATIONdisabled); line: 1, column: 1642] (through reference chain: com.diversium.duckserver.pikvm.api.SwitchStateEvent["edids"]->com.diversium.duckserver.pikvm.api.Edids["all"]->java.util.LinkedHashMap["default"]->java.util.LinkedHashMap["parsed"]->com.diversium.duckserver.pikvm.api.EdidInfo["mfc_id"])

Image

@pjfanning
Copy link
Member

And data objects

api.zip

please provide the classes as text not as a linked zip

@KyryloSemenkoDiversium
Copy link
Author

Sure.

package com.diversium.duckserver.pikvm.api;

/**
 * Marker interface for all events sent to / received from the server
 */
public interface KvmdEvent {
    /**
     * Returns the {@link EventType#getValue()} identifier of the event.
     */
    String getEventType();
}

package com.diversium.duckserver.pikvm.api;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;

public enum EventType {
    HID_STATE("hid_state"),
    MOUSE_RELATIVE("mouse_relative"),
    MOUSE_BUTTON("mouse_button"),
    LOOP("loop"),
    GPIO_STATE("gpio_state"),
    ATX_STATE("atx_state"),
    MSD_STATE("msd_state"),
    OCR_STATE("ocr_state"),
    INFO_STATE("info_state"),
    HID_KEYMAPS_STATE("hid_keymaps_state"),
    SWITCH_STATE("switch_state"),
    STREAMER_STATE("streamer_state"),
    PING("ping"),
    PONG("pong");

    private final String value;

    EventType(String value) {
        this.value = value;
    }

    @JsonValue
    public String getValue() {
        return value;
    }

    @JsonCreator
    public static EventType fromString(String key) {
        if (key == null) return null;
        for (EventType type : values()) {
            if (type.value.equals(key)) {
                return type;
            }
        }
        throw new IllegalArgumentException("Unknown EventType: " + key);
    }
}
package com.diversium.duckserver.pikvm.api;

import java.util.Map;

public class SwitchStateEvent implements KvmdEvent {
    private SwitchStateModel model;
    private Summary summary;
    private Edids edids;
    private Map<String, ColorProfile> colors;
    private Video video;
    private Usb usb;
    private Beacons beacons;
    private Atx atx;

    @Override
    public String getEventType() {
        return EventType.SWITCH_STATE.getValue();
    }

    /**
     * @return The {@link #atx} field value.
     */
    public Atx getAtx() {
        return atx;
    }

    /**
     * @param atx see the {@link #atx} field description.
     */
    public void setAtx(Atx atx) {
        this.atx = atx;
    }

    /**
     * @return The {@link #beacons} field value.
     */
    public Beacons getBeacons() {
        return beacons;
    }

    /**
     * @param beacons see the {@link #beacons} field description.
     */
    public void setBeacons(Beacons beacons) {
        this.beacons = beacons;
    }

    /**
     * @return The {@link #colors} field value.
     */
    public Map<String, ColorProfile> getColors() {
        return colors;
    }

    /**
     * @param colors see the {@link #colors} field description.
     */
    public void setColors(Map<String, ColorProfile> colors) {
        this.colors = colors;
    }

    /**
     * @return The {@link #edids} field value.
     */
    public Edids getEdids() {
        return edids;
    }

    /**
     * @param edids see the {@link #edids} field description.
     */
    public void setEdids(Edids edids) {
        this.edids = edids;
    }

    /**
     * @return The {@link #model} field value.
     */
    public SwitchStateModel getModel() {
        return model;
    }

    /**
     * @param model see the {@link #model} field description.
     */
    public void setModel(SwitchStateModel model) {
        this.model = model;
    }

    /**
     * @return The {@link #summary} field value.
     */
    public Summary getSummary() {
        return summary;
    }

    /**
     * @param summary see the {@link #summary} field description.
     */
    public void setSummary(Summary summary) {
        this.summary = summary;
    }

    /**
     * @return The {@link #usb} field value.
     */
    public Usb getUsb() {
        return usb;
    }

    /**
     * @param usb see the {@link #usb} field description.
     */
    public void setUsb(Usb usb) {
        this.usb = usb;
    }

    /**
     * @return The {@link #video} field value.
     */
    public Video getVideo() {
        return video;
    }

    /**
     * @param video see the {@link #video} field description.
     */
    public void setVideo(Video video) {
        this.video = video;
    }
}

package com.diversium.duckserver.pikvm.api;

import java.util.List;
import java.util.Map;

public class Edids {
    private Map<String, Map<String, EdidInfo>> all;
    private List<Object> used;

    /**
     * @return The {@link #all} field value.
     */
    public Map<String, Map<String, EdidInfo>> getAll() {
        return all;
    }

    /**
     * @param all see the {@link #all} field description.
     */
    public void setAll(Map<String, Map<String, EdidInfo>> all) {
        this.all = all;
    }

    /**
     * @return The {@link #used} field value.
     */
    public List<Object> getUsed() {
        return used;
    }

    /**
     * @param used see the {@link #used} field description.
     */
    public void setUsed(List<Object> used) {
        this.used = used;
    }
}

package com.diversium.duckserver.pikvm.api;

import com.fasterxml.jackson.annotation.JsonProperty;

public class EdidInfo {
    private String name;
    private String data;
    private EdidParsed parsed;

    // Work around
    // The following fields are part of the "EdidParsed" object, but there is the bug https://github.com/FasterXML/jackson-databind/issues/5098
//    @JsonProperty("mfc_id")
//    private String mfcId;
//
//    @JsonProperty("product_id")
//    private int productId;
//
//    @JsonProperty("serial")
//    private long serial;
//
//    @JsonProperty("monitor_name")
//    private String monitorName;
//
//    @JsonProperty("monitor_serial")
//    private String monitorSerial;
//
//    @JsonProperty("audio")
//    private boolean audio;

    public EdidInfo(String name) {
        this.name = name;
    }

    public EdidInfo() {
        // Default constructor
    }

    /**
     * @return The {@link #data} field value.
     */
    public String getData() {
        return data;
    }

    /**
     * @param data see the {@link #data} field description.
     */
    public void setData(String data) {
        this.data = data;
    }

    /**
     * @return The {@link #name} field value.
     */
    public String getName() {
        return name;
    }

    /**
     * @param name see the {@link #name} field description.
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return The {@link #parsed} field value.
     */
    public EdidParsed getParsed() {
        return parsed;
    }

    /**
     * @param parsed see the {@link #parsed} field description.
     */
    public void setParsed(EdidParsed parsed) {
        this.parsed = parsed;
    }

//    /**
//     * @return The {@link #mfcId} field value.
//     */
//    public String getMfcId() {
//        return mfcId;
//    }
//
//    /**
//     * @param mfcId see the {@link #mfcId} field description.
//     */
//    public void setMfcId(String mfcId) {
//        this.mfcId = mfcId;
//    }
//
//    /**
//     * @return The {@link #audio} field value.
//     */
//    public boolean isAudio() {
//        return audio;
//    }
//
//    /**
//     * @param audio see the {@link #audio} field description.
//     */
//    public void setAudio(boolean audio) {
//        this.audio = audio;
//    }
//
//    /**
//     * @return The {@link #monitorName} field value.
//     */
//    public String getMonitorName() {
//        return monitorName;
//    }
//
//    /**
//     * @param monitorName see the {@link #monitorName} field description.
//     */
//    public void setMonitorName(String monitorName) {
//        this.monitorName = monitorName;
//    }
//
//    /**
//     * @return The {@link #monitorSerial} field value.
//     */
//    public String getMonitorSerial() {
//        return monitorSerial;
//    }
//
//    /**
//     * @param monitorSerial see the {@link #monitorSerial} field description.
//     */
//    public void setMonitorSerial(String monitorSerial) {
//        this.monitorSerial = monitorSerial;
//    }
//
//    /**
//     * @return The {@link #productId} field value.
//     */
//    public int getProductId() {
//        return productId;
//    }
//
//    /**
//     * @param productId see the {@link #productId} field description.
//     */
//    public void setProductId(int productId) {
//        this.productId = productId;
//    }
//
//    /**
//     * @return The {@link #serial} field value.
//     */
//    public long getSerial() {
//        return serial;
//    }
//
//    /**
//     * @param serial see the {@link #serial} field description.
//     */
//    public void setSerial(long serial) {
//        this.serial = serial;
//    }
}

package com.diversium.duckserver.pikvm.api;

import java.util.List;

public class SwitchStateModel {
    private List<Object> units;
    private List<Object> ports;
    private Limits limits;

    /**
     * @return The {@link #limits} field value.
     */
    public Limits getLimits() {
        return limits;
    }

    /**
     * @param limits see the {@link #limits} field description.
     */
    public void setLimits(Limits limits) {
        this.limits = limits;
    }

    /**
     * @return The {@link #ports} field value.
     */
    public List<Object> getPorts() {
        return ports;
    }

    /**
     * @param ports see the {@link #ports} field description.
     */
    public void setPorts(List<Object> ports) {
        this.ports = ports;
    }

    /**
     * @return The {@link #units} field value.
     */
    public List<Object> getUnits() {
        return units;
    }

    /**
     * @param units see the {@link #units} field description.
     */
    public void setUnits(List<Object> units) {
        this.units = units;
    }
}

package com.diversium.duckserver.pikvm.api;

public class Limits {
    private AtxLimits atx;

    /**
     * @return The {@link #atx} field value.
     */
    public AtxLimits getAtx() {
        return atx;
    }

    /**
     * @param atx see the {@link #atx} field description.
     */
    public void setAtx(AtxLimits atx) {
        this.atx = atx;
    }
}

package com.diversium.duckserver.pikvm.api;

import com.fasterxml.jackson.annotation.JsonProperty;

public class DelaySettings {
    @JsonProperty("default")
    private double defaultVal;
    private double min;
    private double max;

    /**
     * @return The {@link #defaultVal} field value.
     */
    public double getDefaultVal() {
        return defaultVal;
    }

    /**
     * @param defaultVal see the {@link #defaultVal} field description.
     */
    public void setDefaultVal(double defaultVal) {
        this.defaultVal = defaultVal;
    }

    /**
     * @return The {@link #max} field value.
     */
    public double getMax() {
        return max;
    }

    /**
     * @param max see the {@link #max} field description.
     */
    public void setMax(double max) {
        this.max = max;
    }

    /**
     * @return The {@link #min} field value.
     */
    public double getMin() {
        return min;
    }

    /**
     * @param min see the {@link #min} field description.
     */
    public void setMin(double min) {
        this.min = min;
    }
}

package com.diversium.duckserver.pikvm.api;

import com.fasterxml.jackson.annotation.JsonProperty;

public class Summary {
    @JsonProperty("active_port")
    private int activePort;
    private boolean synced;

    /**
     * @return The {@link #activePort} field value.
     */
    public int getActivePort() {
        return activePort;
    }

    /**
     * @param activePort see the {@link #activePort} field description.
     */
    public void setActivePort(int activePort) {
        this.activePort = activePort;
    }

    /**
     * @return The {@link #synced} field value.
     */
    public boolean isSynced() {
        return synced;
    }

    /**
     * @param synced see the {@link #synced} field description.
     */
    public void setSynced(boolean synced) {
        this.synced = synced;
    }
}

package com.diversium.duckserver.pikvm.api;

import java.util.List;

public class AtxLeds {
    private List<Object> power;
    private List<Object> hdd;

    /**
     * @return The {@link #hdd} field value.
     */
    public List<Object> getHdd() {
        return hdd;
    }

    /**
     * @param hdd see the {@link #hdd} field description.
     */
    public void setHdd(List<Object> hdd) {
        this.hdd = hdd;
    }

    /**
     * @return The {@link #power} field value.
     */
    public List<Object> getPower() {
        return power;
    }

    /**
     * @param power see the {@link #power} field description.
     */
    public void setPower(List<Object> power) {
        this.power = power;
    }
}

package com.diversium.duckserver.pikvm.api;

import com.fasterxml.jackson.annotation.JsonProperty;

public class EdidParsed {
    @JsonProperty("mfc_id")
    private String mfcId;

    @JsonProperty("product_id")
    private int productId;

    @JsonProperty("serial")
    private long serial;

    @JsonProperty("monitor_name")
    private String monitorName;

    @JsonProperty("monitor_serial")
    private String monitorSerial;

    @JsonProperty("audio")
    private boolean audio;

    /**
     * @return The {@link #audio} field value.
     */
    public boolean isAudio() {
        return audio;
    }

    /**
     * @param audio see the {@link #audio} field description.
     */
    public void setAudio(boolean audio) {
        this.audio = audio;
    }

    /**
     * @return The {@link #mfcId} field value.
     */
    public String getMfcId() {
        return mfcId;
    }

    /**
     * @param mfcId see the {@link #mfcId} field description.
     */
    public void setMfcId(String mfcId) {
        this.mfcId = mfcId;
    }

    /**
     * @return The {@link #monitorName} field value.
     */
    public String getMonitorName() {
        return monitorName;
    }

    /**
     * @param monitorName see the {@link #monitorName} field description.
     */
    public void setMonitorName(String monitorName) {
        this.monitorName = monitorName;
    }

    /**
     * @return The {@link #monitorSerial} field value.
     */
    public String getMonitorSerial() {
        return monitorSerial;
    }

    /**
     * @param monitorSerial see the {@link #monitorSerial} field description.
     */
    public void setMonitorSerial(String monitorSerial) {
        this.monitorSerial = monitorSerial;
    }

    /**
     * @return The {@link #productId} field value.
     */
    public int getProductId() {
        return productId;
    }

    /**
     * @param productId see the {@link #productId} field description.
     */
    public void setProductId(int productId) {
        this.productId = productId;
    }

    /**
     * @return The {@link #serial} field value.
     */
    public long getSerial() {
        return serial;
    }

    /**
     * @param serial see the {@link #serial} field description.
     */
    public void setSerial(long serial) {
        this.serial = serial;
    }
}

package com.diversium.duckserver.pikvm.api;

import java.util.List;

public class Atx {
    private List<Object> busy;
    private AtxLeds leds;

    /**
     * @return The {@link #busy} field value.
     */
    public List<Object> getBusy() {
        return busy;
    }

    /**
     * @param busy see the {@link #busy} field description.
     */
    public void setBusy(List<Object> busy) {
        this.busy = busy;
    }

    /**
     * @return The {@link #leds} field value.
     */
    public AtxLeds getLeds() {
        return leds;
    }

    /**
     * @param leds see the {@link #leds} field description.
     */
    public void setLeds(AtxLeds leds) {
        this.leds = leds;
    }
}

package com.diversium.duckserver.pikvm.api;

import com.fasterxml.jackson.annotation.JsonProperty;

public class ColorProfile {
    @JsonProperty("blink_ms")
    private int blinkMs;

    @JsonProperty("brightness")
    private int brightness;

    @JsonProperty("red")
    private int red;

    @JsonProperty("blue")
    private int blue;

    @JsonProperty("green")
    private int green;

    /**
     * @return The {@link #blinkMs} field value.
     */
    public int getBlinkMs() {
        return blinkMs;
    }

    /**
     * @param blinkMs see the {@link #blinkMs} field description.
     */
    public void setBlinkMs(int blinkMs) {
        this.blinkMs = blinkMs;
    }

    /**
     * @return The {@link #blue} field value.
     */
    public int getBlue() {
        return blue;
    }

    /**
     * @param blue see the {@link #blue} field description.
     */
    public void setBlue(int blue) {
        this.blue = blue;
    }

    /**
     * @return The {@link #brightness} field value.
     */
    public int getBrightness() {
        return brightness;
    }

    /**
     * @param brightness see the {@link #brightness} field description.
     */
    public void setBrightness(int brightness) {
        this.brightness = brightness;
    }

    /**
     * @return The {@link #green} field value.
     */
    public int getGreen() {
        return green;
    }

    /**
     * @param green see the {@link #green} field description.
     */
    public void setGreen(int green) {
        this.green = green;
    }

    /**
     * @return The {@link #red} field value.
     */
    public int getRed() {
        return red;
    }

    /**
     * @param red see the {@link #red} field description.
     */
    public void setRed(int red) {
        this.red = red;
    }
}

package com.diversium.duckserver.pikvm.api;

import java.util.List;

public class Video {
    private List<Object> links;

    /**
     * @return The {@link #links} field value.
     */
    public List<Object> getLinks() {
        return links;
    }

    /**
     * @param links see the {@link #links} field description.
     */
    public void setLinks(List<Object> links) {
        this.links = links;
    }
}

package com.diversium.duckserver.pikvm.api;

import java.util.List;

public class Usb {
    private List<Object> links;

    /**
     * @return The {@link #links} field value.
     */
    public List<Object> getLinks() {
        return links;
    }

    /**
     * @param links see the {@link #links} field description.
     */
    public void setLinks(List<Object> links) {
        this.links = links;
    }
}

package com.diversium.duckserver.pikvm.api;

import java.util.List;

public class Beacons {
    private List<Object> uplinks;
    private List<Object> downlinks;
    private List<Object> ports;

    /**
     * @return The {@link #downlinks} field value.
     */
    public List<Object> getDownlinks() {
        return downlinks;
    }

    /**
     * @param downlinks see the {@link #downlinks} field description.
     */
    public void setDownlinks(List<Object> downlinks) {
        this.downlinks = downlinks;
    }

    /**
     * @return The {@link #ports} field value.
     */
    public List<Object> getPorts() {
        return ports;
    }

    /**
     * @param ports see the {@link #ports} field description.
     */
    public void setPorts(List<Object> ports) {
        this.ports = ports;
    }

    /**
     * @return The {@link #uplinks} field value.
     */
    public List<Object> getUplinks() {
        return uplinks;
    }

    /**
     * @param uplinks see the {@link #uplinks} field description.
     */
    public void setUplinks(List<Object> uplinks) {
        this.uplinks = uplinks;
    }
}

@JooHyukKim
Copy link
Member

@KyryloSemenkoDiversium please refer to other issues and minimize the reproduction. U cant just dump hundreds of lines

@pjfanning
Copy link
Member

pjfanning commented Apr 17, 2025

If this was an issue affecting me in work, I would start by creating a Java instance and serializing it to see what the JSON looked like. That might help in finding differences in structure from the JSON you are trying to deserialize.

@cowtowncoder
Copy link
Member

Ok definitely need a stand-alone reproduction, ideally minimal, and without framework dependencies (that is only depending on Jackson code and test classes)
As given it would be difficult to help unfortunately.

@cowtowncoder cowtowncoder removed the to-evaluate Issue that has been received but not yet evaluated label Apr 22, 2025
@cowtowncoder
Copy link
Member

Please try Jackson 2.18.3. This sounds like an issue that we fixed in 2.18.3.

FasterXML/jackson-core#1397

I think "Long" here refers to length of content and not Java Long (number) type.
So probably not related to that issue.

@KyryloSemenkoDiversium
Copy link
Author

Sorry it took so long. Here is an example without external dependencies.

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

import java.util.List;
import java.util.Map;

public class MappingExample {

    public static void main(String[] args) {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.findAndRegisterModules();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
        objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);

        try {
            String json = "{ \"event_type\": \"switch_state\", \"event\": { \"model\": { \"units\": [], \"ports\": [], \"limits\": { \"atx\": { \"click_delays\": { \"power\": { \"default\": 0.5, \"min\": 0, \"max\": 10 }, \"power_long\": { \"default\": 5.5, \"min\": 0, \"max\": 10 }, \"reset\": { \"default\": 0.5, \"min\": 0, \"max\": 10 } } } } }, \"summary\": { \"active_port\": -1, \"synced\": true }, \"edids\": { \"all\": { \"default\": { \"name\": \"Default\", \"data\": \"00FFFFFFFFFFFF0031D8737701010101231A010380351E780E0565A756529C270F50543FED00B300A9C0950090408180814081C0714F023A801871382D40582C45000F282100001E000000FF0043414645424142452020202020000000FD00324B0F5211000A202020202020000000FC0050694B564D20563420506C7573012D020320714B90041F13223E213D203C0167030C001000802D23097F0783010000023A801871382D40582C45000F282100001E011D007251D01E206E2855000F282100001E023A80D072382D40102C45800F282100001E283C80A070B023403020360006442100001A000000000000000000000000000000000000000000000030\", \"parsed\": { \"mfc_id\": \"LNX\", \"product_id\": 30579, \"serial\": 16843009, \"monitor_name\": \"PiKVM V4 Plus\", \"monitor_serial\": \"CAFEBABE\", \"audio\": true } } }, \"used\": [] }, \"colors\": { \"beacon\": { \"blink_ms\": 250, \"brightness\": 255, \"red\": 228, \"blue\": 156, \"green\": 44 }, \"flashing\": { \"blink_ms\": 0, \"brightness\": 128, \"red\": 0, \"blue\": 255, \"green\": 170 }, \"bootloader\": { \"blink_ms\": 0, \"brightness\": 128, \"red\": 255, \"blue\": 0, \"green\": 170 }, \"inactive\": { \"blink_ms\": 0, \"brightness\": 64, \"red\": 255, \"blue\": 0, \"green\": 0 }, \"active\": { \"blink_ms\": 0, \"brightness\": 128, \"red\": 0, \"blue\": 0, \"green\": 255 } }, \"video\": { \"links\": [] }, \"usb\": { \"links\": [] }, \"beacons\": { \"uplinks\": [], \"downlinks\": [], \"ports\": [] }, \"atx\": { \"busy\": [], \"leds\": { \"power\": [], \"hdd\": [] } } } }";
            EventWrapper eventWrapper = objectMapper.readValue(json, EventWrapper.class);
            System.out.println("Received message: " + eventWrapper.getEventType());
        } catch (Throwable t) {
            System.out.println("Exception: " + t.getMessage());
            t.printStackTrace();
        }
    }

    interface KvmdEvent {
        /**
         * Returns the {@link EventType#getValue()} identifier of the event.
         */
        String getEventType();
    }

    enum EventType {
        SWITCH_STATE("switch_state");

        private final String value;
        
        EventType(String value) {
            this.value = value;
        }

        @JsonValue
        public String getValue() {
            return value;
        }

        @JsonCreator
        public static EventType fromString(String key) {
            if (key == null) return null;
            for (EventType type : values()) {
                if (type.value.equals(key)) {
                    return type;
                }
            }
            throw new IllegalArgumentException("Unknown EventType: " + key);
        }
    }

    static class EventWrapper {
        @JsonProperty("event_type")
        private MappingExample.EventType eventType;

        @JsonProperty("event")
        @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "event_type")
        @JsonSubTypes({
                @JsonSubTypes.Type(value = MappingExample.SwitchStateEvent.class, name = "switch_state")
        })
        private MappingExample.KvmdEvent event;

        /**
         * @return The {@link #eventType} field value.
         */
        public MappingExample.EventType getEventType() {
            return eventType;
        }

        /**
         * @param eventType see the {@link #eventType} field description.
         */
        public void setEventType(MappingExample.EventType eventType) {
            this.eventType = eventType;
        }

        /**
         * @return The {@link #event} field value.
         */
        public MappingExample.KvmdEvent getEvent() {
            return event;
        }

        /**
         * @param event see the {@link #event} field description.
         */
        public void setEvent(MappingExample.KvmdEvent event) {
            this.event = event;
        }
    }
    
    static class SwitchStateEvent implements KvmdEvent {
        private SwitchStateModel model;
        private Summary summary;
        private Edids edids;
        private Map<String, ColorProfile> colors;
        private Video video;
        private Usb usb;
        private Beacons beacons;
        private Atx atx;

        @Override
        public String getEventType() {
            return EventType.SWITCH_STATE.getValue();
        }

        /**
         * @return The {@link #atx} field value.
         */
        public Atx getAtx() {
            return atx;
        }

        /**
         * @param atx see the {@link #atx} field description.
         */
        public void setAtx(Atx atx) {
            this.atx = atx;
        }

        /**
         * @return The {@link #beacons} field value.
         */
        public Beacons getBeacons() {
            return beacons;
        }

        /**
         * @param beacons see the {@link #beacons} field description.
         */
        public void setBeacons(Beacons beacons) {
            this.beacons = beacons;
        }

        /**
         * @return The {@link #colors} field value.
         */
        public Map<String, ColorProfile> getColors() {
            return colors;
        }

        /**
         * @param colors see the {@link #colors} field description.
         */
        public void setColors(Map<String, ColorProfile> colors) {
            this.colors = colors;
        }

        /**
         * @return The {@link #edids} field value.
         */
        public Edids getEdids() {
            return edids;
        }

        /**
         * @param edids see the {@link #edids} field description.
         */
        public void setEdids(Edids edids) {
            this.edids = edids;
        }

        /**
         * @return The {@link #model} field value.
         */
        public SwitchStateModel getModel() {
            return model;
        }

        /**
         * @param model see the {@link #model} field description.
         */
        public void setModel(SwitchStateModel model) {
            this.model = model;
        }

        /**
         * @return The {@link #summary} field value.
         */
        public Summary getSummary() {
            return summary;
        }

        /**
         * @param summary see the {@link #summary} field description.
         */
        public void setSummary(Summary summary) {
            this.summary = summary;
        }

        /**
         * @return The {@link #usb} field value.
         */
        public Usb getUsb() {
            return usb;
        }

        /**
         * @param usb see the {@link #usb} field description.
         */
        public void setUsb(Usb usb) {
            this.usb = usb;
        }

        /**
         * @return The {@link #video} field value.
         */
        public Video getVideo() {
            return video;
        }

        /**
         * @param video see the {@link #video} field description.
         */
        public void setVideo(Video video) {
            this.video = video;
        }
    }

    static class Edids {
        private Map<String, Map<String, EdidInfo>> all;
        private List<Object> used;

        /**
         * @return The {@link #all} field value.
         */
        public Map<String, Map<String, EdidInfo>> getAll() {
            return all;
        }

        /**
         * @param all see the {@link #all} field description.
         */
        public void setAll(Map<String, Map<String, EdidInfo>> all) {
            this.all = all;
        }

        /**
         * @return The {@link #used} field value.
         */
        public List<Object> getUsed() {
            return used;
        }

        /**
         * @param used see the {@link #used} field description.
         */
        public void setUsed(List<Object> used) {
            this.used = used;
        }
    }
    
    static class EdidInfo {
        private String name;
        private String data;
        private EdidParsed parsed;

        // The following fields are part of the "EdidParsed" object in the json example, but https://github.com/FasterXML/jackson-databind/issues/5098
//        @JsonProperty("mfc_id")
//        private String mfcId;
//    
//        @JsonProperty("product_id")
//        private int productId;
//    
//        @JsonProperty("serial")
//        private long serial;
//    
//        @JsonProperty("monitor_name")
//        private String monitorName;
//    
//        @JsonProperty("monitor_serial")
//        private String monitorSerial;
//    
//        @JsonProperty("audio")
//        private boolean audio;

        public EdidInfo(String name) {
            this.name = name;
        }

        public EdidInfo() {
            // Default constructor
        }

        /**
         * @return The {@link #data} field value.
         */
        public String getData() {
            return data;
        }

        /**
         * @param data see the {@link #data} field description.
         */
        public void setData(String data) {
            this.data = data;
        }

        /**
         * @return The {@link #name} field value.
         */
        public String getName() {
            return name;
        }

        /**
         * @param name see the {@link #name} field description.
         */
        public void setName(String name) {
            this.name = name;
        }

        /**
         * @return The {@link #parsed} field value.
         */
        public EdidParsed getParsed() {
            return parsed;
        }

        /**
         * @param parsed see the {@link #parsed} field description.
         */
        public void setParsed(EdidParsed parsed) {
            this.parsed = parsed;
        }

//        /**
//         * @return The {@link #mfcId} field value.
//         */
//        public String getMfcId() {
//            return mfcId;
//        }
//    
//        /**
//         * @param mfcId see the {@link #mfcId} field description.
//         */
//        public void setMfcId(String mfcId) {
//            this.mfcId = mfcId;
//        }
//    
//        /**
//         * @return The {@link #audio} field value.
//         */
//        public boolean isAudio() {
//            return audio;
//        }
//    
//        /**
//         * @param audio see the {@link #audio} field description.
//         */
//        public void setAudio(boolean audio) {
//            this.audio = audio;
//        }
//    
//        /**
//         * @return The {@link #monitorName} field value.
//         */
//        public String getMonitorName() {
//            return monitorName;
//        }
//    
//        /**
//         * @param monitorName see the {@link #monitorName} field description.
//         */
//        public void setMonitorName(String monitorName) {
//            this.monitorName = monitorName;
//        }
//    
//        /**
//         * @return The {@link #monitorSerial} field value.
//         */
//        public String getMonitorSerial() {
//            return monitorSerial;
//        }
//    
//        /**
//         * @param monitorSerial see the {@link #monitorSerial} field description.
//         */
//        public void setMonitorSerial(String monitorSerial) {
//            this.monitorSerial = monitorSerial;
//        }
//    
//        /**
//         * @return The {@link #productId} field value.
//         */
//        public int getProductId() {
//            return productId;
//        }
//    
//        /**
//         * @param productId see the {@link #productId} field description.
//         */
//        public void setProductId(int productId) {
//            this.productId = productId;
//        }
//    
//        /**
//         * @return The {@link #serial} field value.
//         */
//        public long getSerial() {
//            return serial;
//        }
//    
//        /**
//         * @param serial see the {@link #serial} field description.
//         */
//        public void setSerial(long serial) {
//            this.serial = serial;
//        }
    }

    static class SwitchStateModel {
        private List<Object> units;
        private List<Object> ports;
        private Limits limits;

        /**
         * @return The {@link #limits} field value.
         */
        public Limits getLimits() {
            return limits;
        }

        /**
         * @param limits see the {@link #limits} field description.
         */
        public void setLimits(Limits limits) {
            this.limits = limits;
        }

        /**
         * @return The {@link #ports} field value.
         */
        public List<Object> getPorts() {
            return ports;
        }

        /**
         * @param ports see the {@link #ports} field description.
         */
        public void setPorts(List<Object> ports) {
            this.ports = ports;
        }

        /**
         * @return The {@link #units} field value.
         */
        public List<Object> getUnits() {
            return units;
        }

        /**
         * @param units see the {@link #units} field description.
         */
        public void setUnits(List<Object> units) {
            this.units = units;
        }
    }

    static class Limits {
        private AtxLimits atx;

        /**
         * @return The {@link #atx} field value.
         */
        public AtxLimits getAtx() {
            return atx;
        }

        /**
         * @param atx see the {@link #atx} field description.
         */
        public void setAtx(AtxLimits atx) {
            this.atx = atx;
        }
    }

    static class DelaySettings {
        @JsonProperty("default")
        private double defaultVal;
        private double min;
        private double max;

        /**
         * @return The {@link #defaultVal} field value.
         */
        public double getDefaultVal() {
            return defaultVal;
        }

        /**
         * @param defaultVal see the {@link #defaultVal} field description.
         */
        public void setDefaultVal(double defaultVal) {
            this.defaultVal = defaultVal;
        }

        /**
         * @return The {@link #max} field value.
         */
        public double getMax() {
            return max;
        }

        /**
         * @param max see the {@link #max} field description.
         */
        public void setMax(double max) {
            this.max = max;
        }

        /**
         * @return The {@link #min} field value.
         */
        public double getMin() {
            return min;
        }

        /**
         * @param min see the {@link #min} field description.
         */
        public void setMin(double min) {
            this.min = min;
        }
    }
    
    static class Summary {
        @JsonProperty("active_port")
        private int activePort;
        private boolean synced;

        /**
         * @return The {@link #activePort} field value.
         */
        public int getActivePort() {
            return activePort;
        }

        /**
         * @param activePort see the {@link #activePort} field description.
         */
        public void setActivePort(int activePort) {
            this.activePort = activePort;
        }

        /**
         * @return The {@link #synced} field value.
         */
        public boolean isSynced() {
            return synced;
        }

        /**
         * @param synced see the {@link #synced} field description.
         */
        public void setSynced(boolean synced) {
            this.synced = synced;
        }
    }

    static class AtxLeds {
        private List<Object> power;
        private List<Object> hdd;

        /**
         * @return The {@link #hdd} field value.
         */
        public List<Object> getHdd() {
            return hdd;
        }

        /**
         * @param hdd see the {@link #hdd} field description.
         */
        public void setHdd(List<Object> hdd) {
            this.hdd = hdd;
        }

        /**
         * @return The {@link #power} field value.
         */
        public List<Object> getPower() {
            return power;
        }

        /**
         * @param power see the {@link #power} field description.
         */
        public void setPower(List<Object> power) {
            this.power = power;
        }
    }

    static class EdidParsed {
        @JsonProperty("mfc_id")
        private String mfcId;

        @JsonProperty("product_id")
        private int productId;

        @JsonProperty("serial")
        private long serial;

        @JsonProperty("monitor_name")
        private String monitorName;

        @JsonProperty("monitor_serial")
        private String monitorSerial;

        @JsonProperty("audio")
        private boolean audio;

        /**
         * @return The {@link #audio} field value.
         */
        public boolean isAudio() {
            return audio;
        }

        /**
         * @param audio see the {@link #audio} field description.
         */
        public void setAudio(boolean audio) {
            this.audio = audio;
        }

        /**
         * @return The {@link #mfcId} field value.
         */
        public String getMfcId() {
            return mfcId;
        }

        /**
         * @param mfcId see the {@link #mfcId} field description.
         */
        public void setMfcId(String mfcId) {
            this.mfcId = mfcId;
        }

        /**
         * @return The {@link #monitorName} field value.
         */
        public String getMonitorName() {
            return monitorName;
        }

        /**
         * @param monitorName see the {@link #monitorName} field description.
         */
        public void setMonitorName(String monitorName) {
            this.monitorName = monitorName;
        }

        /**
         * @return The {@link #monitorSerial} field value.
         */
        public String getMonitorSerial() {
            return monitorSerial;
        }

        /**
         * @param monitorSerial see the {@link #monitorSerial} field description.
         */
        public void setMonitorSerial(String monitorSerial) {
            this.monitorSerial = monitorSerial;
        }

        /**
         * @return The {@link #productId} field value.
         */
        public int getProductId() {
            return productId;
        }

        /**
         * @param productId see the {@link #productId} field description.
         */
        public void setProductId(int productId) {
            this.productId = productId;
        }

        /**
         * @return The {@link #serial} field value.
         */
        public long getSerial() {
            return serial;
        }

        /**
         * @param serial see the {@link #serial} field description.
         */
        public void setSerial(long serial) {
            this.serial = serial;
        }
    }

    static class Atx {
        private List<Object> busy;
        private AtxLeds leds;

        /**
         * @return The {@link #busy} field value.
         */
        public List<Object> getBusy() {
            return busy;
        }

        /**
         * @param busy see the {@link #busy} field description.
         */
        public void setBusy(List<Object> busy) {
            this.busy = busy;
        }

        /**
         * @return The {@link #leds} field value.
         */
        public AtxLeds getLeds() {
            return leds;
        }

        /**
         * @param leds see the {@link #leds} field description.
         */
        public void setLeds(AtxLeds leds) {
            this.leds = leds;
        }
    }

    static class ColorProfile {
        @JsonProperty("blink_ms")
        private int blinkMs;

        @JsonProperty("brightness")
        private int brightness;

        @JsonProperty("red")
        private int red;

        @JsonProperty("blue")
        private int blue;

        @JsonProperty("green")
        private int green;

        /**
         * @return The {@link #blinkMs} field value.
         */
        public int getBlinkMs() {
            return blinkMs;
        }

        /**
         * @param blinkMs see the {@link #blinkMs} field description.
         */
        public void setBlinkMs(int blinkMs) {
            this.blinkMs = blinkMs;
        }

        /**
         * @return The {@link #blue} field value.
         */
        public int getBlue() {
            return blue;
        }

        /**
         * @param blue see the {@link #blue} field description.
         */
        public void setBlue(int blue) {
            this.blue = blue;
        }

        /**
         * @return The {@link #brightness} field value.
         */
        public int getBrightness() {
            return brightness;
        }

        /**
         * @param brightness see the {@link #brightness} field description.
         */
        public void setBrightness(int brightness) {
            this.brightness = brightness;
        }

        /**
         * @return The {@link #green} field value.
         */
        public int getGreen() {
            return green;
        }

        /**
         * @param green see the {@link #green} field description.
         */
        public void setGreen(int green) {
            this.green = green;
        }

        /**
         * @return The {@link #red} field value.
         */
        public int getRed() {
            return red;
        }

        /**
         * @param red see the {@link #red} field description.
         */
        public void setRed(int red) {
            this.red = red;
        }
    }
    
    static class Video {
        private List<Object> links;

        /**
         * @return The {@link #links} field value.
         */
        public List<Object> getLinks() {
            return links;
        }

        /**
         * @param links see the {@link #links} field description.
         */
        public void setLinks(List<Object> links) {
            this.links = links;
        }
    }

    static class Usb {
        private List<Object> links;

        /**
         * @return The {@link #links} field value.
         */
        public List<Object> getLinks() {
            return links;
        }

        /**
         * @param links see the {@link #links} field description.
         */
        public void setLinks(List<Object> links) {
            this.links = links;
        }
    }

    static class Beacons {
        private List<Object> uplinks;
        private List<Object> downlinks;
        private List<Object> ports;

        /**
         * @return The {@link #downlinks} field value.
         */
        public List<Object> getDownlinks() {
            return downlinks;
        }

        /**
         * @param downlinks see the {@link #downlinks} field description.
         */
        public void setDownlinks(List<Object> downlinks) {
            this.downlinks = downlinks;
        }

        /**
         * @return The {@link #ports} field value.
         */
        public List<Object> getPorts() {
            return ports;
        }

        /**
         * @param ports see the {@link #ports} field description.
         */
        public void setPorts(List<Object> ports) {
            this.ports = ports;
        }

        /**
         * @return The {@link #uplinks} field value.
         */
        public List<Object> getUplinks() {
            return uplinks;
        }

        /**
         * @param uplinks see the {@link #uplinks} field description.
         */
        public void setUplinks(List<Object> uplinks) {
            this.uplinks = uplinks;
        }
    }

    static class AtxLimits {
        @JsonProperty("click_delays")
        private Map<String, DelaySettings> clickDelays;

        /**
         * @return The {@link #clickDelays} field value.
         */
        public Map<String, DelaySettings> getClickDelays() {
            return clickDelays;
        }

        /**
         * @param clickDelays see the {@link #clickDelays} field description.
         */
        public void setClickDelays(Map<String, DelaySettings> clickDelays) {
            this.clickDelays = clickDelays;
        }
    }
}

Image

Unrecognized field "mfc_id" (class com.diversium.duckserver.example.MappingExample$EdidInfo), not marked as ignorable (3 known properties: "parsed", "data", "name"])

@Dokeyy
Copy link

Dokeyy commented Apr 30, 2025

I think the type of the field all in Edids class should be Map<String, EdidInfo> rather than Map<String, Map<String, EdidInfo>> .

@KyryloSemenkoDiversium
Copy link
Author

Yes, that's true. I apologize for the incorrect reporting. It's not a bug in Mapper, but in my implementation.

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

No branches or pull requests

5 participants