1. 🧭 High-Level Summary

MapDeserializer is Fastjson’s built-in deserializer for converting JSON objects into Java Map implementations. It handles raw maps, parameterized maps such as Map<String, User>, specialized implementations such as TreeMap, ConcurrentHashMap, EnumMap, and some JSON-specific features including $ref references, ordered fields, @type/auto-type handling, null values, and resolve tasks. It is used internally by DefaultJSONParser whenever Fastjson needs to deserialize JSON into a map-like target type.

At a high level, the class selects an appropriate concrete map, reads JSON object keys and values from the lexer, delegates value conversion to other Fastjson deserializers, stores the results, and restores the parser’s ParseContext after completion.


2. 🔌 Public API Surface

Public class and singleton

public class MapDeserializer
extends ContextObjectDeserializer
implements ObjectDeserializer {
    public static MapDeserializer instance = new MapDeserializer();

Public methods

Method Purpose
deserialze(DefaultJSONParser, Type, Object, String, int) Main ObjectDeserializer entry point. Converts the current JSON input into the requested map type.
parseMap(DefaultJSONParser, Map<String,Object>, Type, Object) Parses a JSON object into an existing map with a specified value type.
parseMap(DefaultJSONParser, Map<String,Object>, Type, Object, int) Same as above, with parser feature flags.
parseMap(DefaultJSONParser, Map<Object,Object>, Type, Type, Object) Parses maps with explicitly typed keys and values.
createMap(Type) Creates a map instance for the requested type.
createMap(Type, int) Creates a map instance while considering feature flags such as ordered-field handling.
getFastMatchToken() Returns the lexer token most suitable for quickly identifying a JSON object: token 12, representing {.

Intended contract

Consumers provide:

The result is generally:

Example:

Type type = new TypeReference<Map<String, Integer>>() {}.getType();
Map<String, Integer> result =
    parser.parseObject(type);

The normal client-facing API is usually JSON.parseObject(...); callers rarely invoke MapDeserializer directly.

Error behavior

Typical failures include:

The decompiled source contains malformed control flow such as throw var12 in a finally block. Those constructs are decompiler artifacts; the original bytecode almost certainly returned the parsed result while restoring parser context in finally.

Thread safety

Compatibility concerns


3. 🏗️ Architectural Overview

The main components are:

The class uses several recognizable patterns:


4. 🔄 Execution Flow

Main deserialization path

  1. The caller requests deserialization into a target Type.

  2. If the type is exactly JSONObject and no field-type resolver is active, parsing is delegated directly to:

    return parser.parseObject();
  3. If the current lexer token represents JSON null, the lexer advances and the method returns null.

  4. The code detects whether the target is Collections$UnmodifiableMap.

  5. It creates a mutable backing map using createMap.

  6. The current ParseContext is saved.

  7. A new context is established for the map being populated.

  8. The overloaded protected deserialze method is called.

  9. If the target was an unmodifiable map, the populated map is wrapped using Collections.unmodifiableMap.

  10. The original parser context is restored in a finally block.

  11. The parsed map is returned.

Parameterized map path

For a type such as Map<String, User>:

  1. The ParameterizedType is inspected.
  2. The key type and value type are extracted.
  3. A special case treats Spring’s LinkedMultiValueMap as having List values.
  4. If the key type is String, the optimized string-key overload is used.
  5. Otherwise, the fully typed key/value overload is used.
if (String.class == keyType) {
    return MapDeserializer.parseMap(parser, map, valueType, fieldName, features);
}
return MapDeserializer.parseMap(parser, map, keyType, valueType, fieldName);

String-key map path

The string-key parser:

  1. Verifies that the current token is {.
  2. Accepts a limited null representation when the lexer token is a string containing null or an empty string.
  3. Produces a detailed syntax error if the input is not object-like.
  4. Optionally attempts to parse an array and unwrap a single JSONObject element for compatibility.
  5. Skips whitespace and optional arbitrary commas.
  6. Reads a key.
  7. Requires a colon after the key.
  8. Parses the value using the requested valueType.
  9. Inserts the key/value pair into the map.
  10. Calls parser.checkMapResolve for reference-resolution support.
  11. Restores parser context after each value.
  12. Stops at } or reports an invalid terminating token.

The decompiled method is heavily damaged around this logic, but these operations are evident from the surviving statements.

Fully typed key/value path

For non-String keys:

  1. The method verifies that the input begins with { or an appropriate intermediate token.
  2. It obtains deserializers for both keyType and valueType.
  3. It advances the lexer using the key deserializer’s fast-match token.
  4. It handles an empty object.
  5. It checks for special reference syntax when the lexer identifies a reference.
  6. It converts keys using the key deserializer.
  7. It converts values using the value deserializer.
  8. It inserts entries into the target map.
  9. It handles commas and the closing }.
  10. It restores the original parser context.

5. 🧩 Key Classes and Methods

deserialze(...)

Responsibility

Main entry point required by ObjectDeserializer.

Inputs

Output

The requested map object, null, or an exception.

Important logic

Protected deserialze(...) overloads

protected Object deserialze(DefaultJSONParser parser, Type type, Object fieldName, Map map) {
    return this.deserialze(parser, type, fieldName, map, 0);
}

These methods decide whether to:

The raw-map fallback allows the parser to infer generic object/value representations.

parseMap with valueType

Responsibility

Optimized parsing for maps whose keys are strings.

Important behavior

parseMap with keyType and valueType

Responsibility

Parsing maps with explicitly typed keys and values.

Important behavior

The visible reference logic includes:

if ("..".equals(ref)) {
    ParseContext parentContext = context.parent;
    object = parentContext.object;
} else {
    if ("$".equals(ref)) {
        ParseContext rootContext = context;
        while (rootContext.parent != null) {
            rootContext = rootContext.parent;
        }
        object = rootContext.object;

This means:

createMap(Type, int)

Responsibility

Selects the concrete map implementation.

Supported mappings include:

if (type == Map.class) {
    return featrues & Feature.OrderedField.mask != 0 ? new LinkedHashMap() : new HashMap();
}

getFastMatchToken()

Returns 12, which corresponds to the lexer’s object-start token. This allows Fastjson to quickly determine that this deserializer is suitable for a JSON object.


6. ⚙️ Important Implementation Details

Map implementation selection

The requested type determines the result:

This is important because the declared interface does not necessarily determine ordering or concurrency semantics.

Generic type handling

Parameterized types are inspected using reflection:

Type keyType = parameterizedType.getActualTypeArguments()[0];
Type valueType = null;
if (map.getClass().getName().equals("org.springframework.util.LinkedMultiValueMap")) {
    valueType = List.class;
} else {
    valueType = parameterizedType.getActualTypeArguments()[1];
}

The Spring-specific check is a compatibility workaround. It uses the runtime map class name rather than a direct dependency on Spring.

Parser context management

Map parsing is context-sensitive because nested objects and references need to know:

The code repeatedly saves and restores ParseContext. This is necessary for $ref resolution and correct nested parsing, but it makes the control flow difficult to follow.

Special reference handling

Fastjson recognizes references such as:

{
  "parent": {
    "$ref": ".."
  }
}

and:

{
  "root": {
    "$ref": "$"
  }
}

Reference detection can be disabled using Feature.DisableSpecialKeyDetect.

Auto-type handling

The surviving decompiled fragment refers to config.checkAutoType(...), indicating support for type metadata such as @type. Auto-type can cause the parser to instantiate classes specified by input data, so this behavior must be controlled carefully in production.

Complexity

For a map with n entries:

Edge cases and risks


7. 🌿 High-Branching / Hard-to-Read Paths

The main deserialze method

This path combines:

In plain English:

Determine whether this is a special JSON object or null. Otherwise create the correct mutable map, parse into it under a new parser context, optionally wrap it as unmodifiable, and always restore the previous context.

String-key parseMap

This is condition-heavy because it simultaneously handles:

In plain English:

Repeatedly read a property name, require a colon, parse its value, insert it, and continue until }—while also allowing several compatibility and reference-resolution behaviors.

Typed-key parseMap

This path is harder to reason about because key and value parsing are delegated independently, and special references interrupt normal key/value processing.

In plain English:

Obtain converters for the key and value types, parse each entry using those converters, but first check whether the object is actually a special reference that must be resolved immediately or deferred.

Decompiled control flow

The source contains fragments such as:

finally {
    parser.setContext(context);
    throw var12;
}

and:

var var11 = t;

These are clear signs that the decompiler failed to reconstruct local variables, return statements, and exception tables. They should not be interpreted literally as the intended Java source. The original implementation likely had normal return statements around a try/finally context restoration pattern.


8. 💡 Improvement Suggestions

Public API design

Readability