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.
public class MapDeserializer
extends ContextObjectDeserializer
implements ObjectDeserializer {
public static MapDeserializer instance = new MapDeserializer();
MapDeserializer is publicly accessible.instance is the shared singleton normally registered in Fastjson’s deserializer configuration.final, so consumers could replace it.| 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 {. |
Consumers provide:
DefaultJSONParserType, for example:Map.classHashMap.classMap<String, Integer>EnumMap<MyEnum, String>The result is generally:
Mapnull if the JSON value is JSON nullExample:
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.
Typical failures include:
JSONException when the current token is not an object or cannot be interpreted as oneJSONException for a missing colon, malformed reference, unsupported map type, or failed map instantiationTreeMap rejecting non-comparable keysEnumMap receiving an invalid enum keyHashtable rejecting null keys or valuesThe 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.
final, so publication and replacement are not strongly protected.ConcurrentHashMap is concurrent, while HashMap and LinkedHashMap are not.deserialze, including Fastjson’s historical spelling. It should not be renamed in compatible integrations.OrderedField, DisableSpecialKeyDetect, and auto-type configuration.The main components are:
MapDeserializerDefaultJSONParserJSONLexerObjectDeserializerParseContextJSONObject / JSONArrayFeatureThe class uses several recognizable patterns:
ObjectDeserializer instances.createMap selects and instantiates a concrete map implementation.$ref, .., and $ are interpreted using parser context.The caller requests deserialization into a target Type.
If the type is exactly JSONObject and no field-type resolver is active, parsing is delegated directly to:
return parser.parseObject();
If the current lexer token represents JSON null, the lexer advances and the method returns null.
The code detects whether the target is Collections$UnmodifiableMap.
It creates a mutable backing map using createMap.
The current ParseContext is saved.
A new context is established for the map being populated.
The overloaded protected deserialze method is called.
If the target was an unmodifiable map, the populated map is wrapped using Collections.unmodifiableMap.
The original parser context is restored in a finally block.
The parsed map is returned.
For a type such as Map<String, User>:
ParameterizedType is inspected.LinkedMultiValueMap as having List values.String, the optimized string-key overload is used.if (String.class == keyType) {
return MapDeserializer.parseMap(parser, map, valueType, fieldName, features);
}
return MapDeserializer.parseMap(parser, map, keyType, valueType, fieldName);
The string-key parser:
{.null representation when the lexer token is a string containing null or an empty string.JSONObject element for compatibility.valueType.parser.checkMapResolve for reference-resolution support.} or reports an invalid terminating token.The decompiled method is heavily damaged around this logic, but these operations are evident from the surviving statements.
For non-String keys:
{ or an appropriate intermediate token.keyType and valueType.}.deserialze(...)Responsibility
Main entry point required by ObjectDeserializer.
Inputs
DefaultJSONParser parser: current parsing sessionType type: requested map typeObject fieldName: enclosing field name or indexString format: optional format, not materially used in the visible codeint features: parsing featuresOutput
The requested map object, null, or an exception.
Important logic
JSONObjectnulldeserialze(...) overloadsprotected Object deserialze(DefaultJSONParser parser, Type type, Object fieldName, Map map) {
return this.deserialze(parser, type, fieldName, map, 0);
}
These methods decide whether to:
parser.parseObject(map, fieldName) for raw mapsThe raw-map fallback allows the parser to infer generic object/value representations.
parseMap with valueTypeResponsibility
Optimized parsing for maps whose keys are strings.
Important behavior
valueTypeparseMap with keyType and valueTypeResponsibility
Parsing maps with explicitly typed keys and values.
Important behavior
$ref handlingThe 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:
"$ref": ".." resolves to the parent object."$ref": "quot; resolves to the root object.createMap(Type, int)Responsibility
Selects the concrete map implementation.
Supported mappings include:
Properties → PropertiesHashtable → HashtableIdentityHashMap → IdentityHashMapSortedMap / TreeMap → TreeMapConcurrentMap / ConcurrentHashMap → ConcurrentHashMapMap → HashMap or LinkedHashMap, depending on OrderedFieldHashMap → HashMapLinkedHashMap → LinkedHashMapEnumMap<K,V> → EnumMapif (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.
The requested type determines the result:
Map normally produces HashMap.Feature.OrderedField, declared Map produces LinkedHashMap.SortedMap produces TreeMap.ConcurrentMap produces ConcurrentHashMap.Class.newInstance().Collections$UnmodifiableMap is populated using a mutable HashMap and wrapped afterward.This is important because the declared interface does not necessarily determine ordering or concurrency semantics.
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.
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.
Fastjson recognizes references such as:
{
"parent": {
"$ref": ".."
}
}
and:
{
"root": {
"$ref": "$"
}
}
Reference detection can be disabled using Feature.DisableSpecialKeyDetect.
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.
For a map with n entries:
HashMap: expected O(1) insertionLinkedHashMap: expected O(1) insertionTreeMap: O(log n) insertionConcurrentHashMap: expected O(1) insertionnull returns null.JSONObject may be accepted as a compatibility fallback.TreeMap may fail for keys that are not mutually comparable.EnumMap requires a valid enum key type and appropriate constructor behavior.Hashtable and ConcurrentHashMap do not allow null values, unlike HashMap.finally blocks are not valid source-level representations of the intended return behavior.deserialze methodThis path combines:
JSONObjectIn 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.
parseMapThis 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.
parseMapThis 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.
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.
Make the singleton immutable:
public static final MapDeserializer instance = new MapDeserializer();
Prefer a correctly spelled public entry point such as deserialize in new APIs, while retaining deserialze for compatibility.
Replace raw Map and ObjectDeserializer signatures with generics where possible.
Document whether map instances are mutable, ordered, concurrent, or unmodifiable.
Expose a safer configuration mechanism for auto-type behavior.
readStringKeyreadTypedKeyreadMapValuehandleReferencefinishMapEntry12, 13, 15, and 20 with named constants or clearly documented enum values.i, ch, tok, and clazz.