private void readAnimation(String name, JsonValue map, SkeletonData skeletonData) {
    Array<Timeline> timelines = new Array();
    float duration = 0;

    for (JsonValue boneMap = map.getChild("bones"); boneMap != null; boneMap = boneMap.next()) {
      int boneIndex = skeletonData.findBoneIndex(boneMap.name());
      if (boneIndex == -1) throw new SerializationException("Bone not found: " + boneMap.name());

      for (JsonValue timelineMap = boneMap.child();
          timelineMap != null;
          timelineMap = timelineMap.next()) {
        String timelineName = timelineMap.name();
        if (timelineName.equals(TIMELINE_ROTATE)) {
          RotateTimeline timeline = new RotateTimeline(timelineMap.size());
          timeline.setBoneIndex(boneIndex);

          int frameIndex = 0;
          for (JsonValue valueMap = timelineMap.child();
              valueMap != null;
              valueMap = valueMap.next()) {
            float time = valueMap.getFloat("time");
            timeline.setFrame(frameIndex, time, valueMap.getFloat("angle"));
            readCurve(timeline, frameIndex, valueMap);
            frameIndex++;
          }
          timelines.add(timeline);
          duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 2 - 2]);

        } else if (timelineName.equals(TIMELINE_TRANSLATE) || timelineName.equals(TIMELINE_SCALE)) {
          TranslateTimeline timeline;
          float timelineScale = 1;
          if (timelineName.equals(TIMELINE_SCALE)) timeline = new ScaleTimeline(timelineMap.size());
          else {
            timeline = new TranslateTimeline(timelineMap.size());
            timelineScale = scale;
          }
          timeline.setBoneIndex(boneIndex);

          int frameIndex = 0;
          for (JsonValue valueMap = timelineMap.child();
              valueMap != null;
              valueMap = valueMap.next()) {
            float time = valueMap.getFloat("time");
            float x = valueMap.getFloat("x", 0), y = valueMap.getFloat("y", 0);
            timeline.setFrame(frameIndex, time, x * timelineScale, y * timelineScale);
            readCurve(timeline, frameIndex, valueMap);
            frameIndex++;
          }
          timelines.add(timeline);
          duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 3 - 3]);

        } else
          throw new RuntimeException(
              "Invalid timeline type for a bone: " + timelineName + " (" + boneMap.name() + ")");
      }
    }

    for (JsonValue slotMap = map.getChild("slots"); slotMap != null; slotMap = slotMap.next()) {
      int slotIndex = skeletonData.findSlotIndex(slotMap.name());

      for (JsonValue timelineMap = slotMap.child();
          timelineMap != null;
          timelineMap = timelineMap.next()) {
        String timelineName = timelineMap.name();
        if (timelineName.equals(TIMELINE_COLOR)) {
          ColorTimeline timeline = new ColorTimeline(timelineMap.size());
          timeline.setSlotIndex(slotIndex);

          int frameIndex = 0;
          for (JsonValue valueMap = timelineMap.child();
              valueMap != null;
              valueMap = valueMap.next()) {
            float time = valueMap.getFloat("time");
            Color color = Color.valueOf(valueMap.getString("color"));
            timeline.setFrame(frameIndex, time, color.r, color.g, color.b, color.a);
            readCurve(timeline, frameIndex, valueMap);
            frameIndex++;
          }
          timelines.add(timeline);
          duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 5 - 5]);

        } else if (timelineName.equals(TIMELINE_ATTACHMENT)) {
          AttachmentTimeline timeline = new AttachmentTimeline(timelineMap.size());
          timeline.setSlotIndex(slotIndex);

          int frameIndex = 0;
          for (JsonValue valueMap = timelineMap.child();
              valueMap != null;
              valueMap = valueMap.next()) {
            float time = valueMap.getFloat("time");
            timeline.setFrame(frameIndex++, time, valueMap.getString("name"));
          }
          timelines.add(timeline);
          duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() - 1]);

        } else
          throw new RuntimeException(
              "Invalid timeline type for a slot: " + timelineName + " (" + slotMap.name() + ")");
      }
    }

    timelines.shrink();
    skeletonData.addAnimation(new Animation(name, timelines, duration));
  }