Every sample you paste is merged position by position before any Kotlin is written, and the types promise only what the samples showed. A key with a value in every sample is non-null, such as val login: String. One that is null in some sample is nullable with no default, String?, and one missing from some objects is nullable with a default, String? = null. The default is the part that matters. kotlinx.serialization treats a property as optional only when it has a default value; without one, a missing key fails with "Field 'license' is required for type with serial name 'Repo', but it was missing", even though the type allows null. The default also changes what is written back, because kotlinx.serialization leaves out a property whose value equals its default: a key that was missing stays missing, while a nullable property with no default is written as null. Unknown keys are refused with "Encountered an unknown key" until the Json instance is built with ignoreUnknownKeys = true.
Integers are Long and other numbers Double. kotlinx.serialization will not read 1.0 into a Long, failing with "Unexpected symbol '.' in numeric literal", so a property is Long only when every sample is written without a fraction or an exponent. A value neither holds exactly, such as 18446744073709551615, is a JsonPrimitive with kotlinx.serialization, whose content keeps the digits as written; BigInteger or BigDecimal with Gson; and Double, with a warning, with Moshi, which has no adapter for either. kotlinx.serialization has no serializer for Any, so a property whose type differs between samples is a JsonElement there, and Any? with Moshi or Gson, which fill it with Double for every number. Dates stay String, which every library reads without extra setup.
Property names are lowerCamelCase from the key, and the key itself goes in the annotation, so user_id, user-id and userId all give userId. A hard keyword such as class, fun or val keeps its name in backticks, `class`, while soft keywords such as value or data are ordinary names. Keys that give the same name are numbered, and a class never takes a name the code refers to, such as Serializable or JsonElement, which it would shadow. With Moshi, @JsonClass(generateAdapter = true) needs Moshi’s code generator applied through KSP or kapt, or Moshi fails with "Failed to find the generated JsonAdapter class". Gson is the trap. It knows nothing about Kotlin and creates objects without calling their constructor, so it ignores non-null types and, unless every property has one, default values: a val name: String can hold null after decoding, and the NullPointerException comes later, wherever name is first used.